JavaScript this
當前執行代碼的環境對象,在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值。(MDN)
this 指向的是當前的代碼上下文環境,所以不同情況下的 this 指向也不同。
1. 全局下的 this
在全局環境下,this
指向全局對象。
全局對象和宿主環境相關,在瀏覽器下,全局對象就是 window
對象,在 node.js
中,全局對象是 global
對象。
window === this; // 輸出:true
新的標準提供了
globalThis
關鍵字來獲取全局對象,這樣就能抹平宿主的差異來操作處理全局對象了。
2. 函數中的 this
函數在不同情況下,其 this
的指向也不同。
2.1 對象下的方法
方法也是一個函數,如果通過對象調用一個函數,函數的 this
就會指向這個對象。
var person = {
age: 14,
name: '鴿子王',
skill: '放鴿子',
say: function() {
console.log('來一段自我介紹:');
console.log('我是' + this.name);
console.log('我今年' + this.age + '歲');
console.log('我最擅長' + this.skill);
},
};
person.say();
say函數
作為對象下的方法,在被調用后,其 this
指向的是他所在的對象,在這里就是 person
對象。
2.2 原型鏈上方法的 this
原型鏈上的方法,this 指向的也是調用該方法的對象。
var __proto__ = {
sum: function() {
return this.number1 + this.number2;
},
};
var object = Object.create(__proto__);
object.number1 = 1;
object.number2 = 2;
console.log(
object.sum(),
); // 輸出:3
Object.create
做就就是將參數作為原型,創建一個對象。
所以 object
的第一原型就是 __proto__
對象。
number1
和 number2
都是 object
變量的屬性,但卻可以被 sum
方法中的 this
訪問到,所以在原型鏈的方法中,this 指向的就是調用該方法的對象。
2.3 getter / setter 下的 this
getter
和 setter
下的 this 也會指向調用該 getter
和 setter
的對象。
var object = {
_name: '鴿子王',
get name() {
return this._name;
},
set name(val) {
console.log(val);
this._name = val;
}
};
console.log(object.name); // 輸出:鴿子王
object.name = '鴿子天王'; // 輸出:鴿子天王
console.log(object.name); // 輸出:鴿子天王
getter
和 setter
本質上也可以理解成兩個函數,作為對象下的函數,在調用的時候 this
也會指向該對象。
2.4 作為 DOM 節點的事件處理器
作為 DOM 節點的事件處理器的時,函數的 this
會指向這個 DOM 對象。
<div>
<button>點擊我</button>
</div>
<script>
document.querySelector('button').addEventListener('click', function() {
this.innerHTML = '被點擊了!';
});
</script>
2.5 作為一個內聯的事件處理器
內聯的事件處理器,其 this
指向的是 DOM 節點自身。
<div>
<button onclick="console.log(this); console.log(this === document.querySelector('button'))">點擊我</button>
</div>
這個規則有局限性,只有最外層的 this 符合這個規則。
<div>
<button onclick="function test() { console.log(this) }; test();">點擊我</button>
</div>
test
函數的 this 指向的是全局對象 window
。
2.6 其他大部分情況下
排開上述的幾個情況,剩下的函數大部分情況下在調用時,this 指向的是全局對象,在瀏覽器中就是 window
對象。
function fn() {
console.log(this);
console.log(this === window);
}
fn();
這樣調用函數,其 this 指向的就是 window 對象了。
有的時候可能會搞混以下情況:
var object = {
username: '咸魚',
fn: function() {
console.log(this.username);
function thisTest() {
console.log(this.username);
console.log(this === window);
}
thisTest();
},
};
object.fn();
這里 thisTest
方法輸出的 username
就會是個 undefined,因為他的 this 指向的是 window,因為他不屬于 object
對象的一個方法,所以 this 就指向了 window。
在回調函數中經常會碰到這個問題:
var info = {
account: '123',
password: '456',
login: function(cb) {
setTimeout(function() {
cb({
account: this.account,
password: this.password,
});
}, 1000);
}
};
info.login(function(info) {
console.log(info);
});
這里回調函數獲取的賬號和密碼是 undefined
,原因就是 this 的指向問題。
通常會使用保留上層 this 的方式解決這個問題。
var info = {
account: '123',
password: '456',
login: function(cb) {
var _this = this;
setTimeout(function() {
cb({
account: _this.account,
password: _this.password,
});
}, 1000);
}
};
info.login(function(info) {
console.log(info);
});
這樣就能解決這個問題。
另外一個情況也很容易混淆 this :
var object = {
user: 'no.1',
say: function() {
console.log(this.user);
},
};
var say = object.say;
object.say(); // 輸出:"no.1"
say(); // 輸出:undefined
這是因為把 object
下的 say
方法單獨賦值給 say 變量的時候,其就作為了 window 下的一個方法,所以他的 this 指向的是 window。
在嚴格模式中,這種情況下的 this
會變成 undefined
。
2.7 構造函數
在 JavaScript 構造函數也被成為 對象構造器
,用于產生對象。
構造函數的聲明和普通函數幾乎沒有區別:
function Point(x, y) {
this.x = x;
this.y = y;
}
var point = new Point(1, 2);
console.log(point.x); // 輸出:1
console.log(point.y); // 輸出:2
構造函數使用 new
關鍵字來構造對象。所以當一個函數被使用 new
關鍵字調用時,這個函數就會作為一個構造函數。
在一個構造函數被調用后,其內部的 this
會指向一個對象,具體的內容可以參考 構造函數
章節。
3. 修改this
3.1 call 方法和 apply 方法
函數具有 call
方法和 apply
方法,這兩個方法可以在調用函數的時候指定函數的 this。
var object = {
user: 'no.1',
};
function say() {
console.log(this.user);
}
say(); // 輸出:undefined
say.call(object); // 輸出:"no.1"
say.apply(object); // 輸出:"no.1"
通過 call
和 apply
方法將 say 函數執行時候的 this 設置為 object
對象。
call 方法從第二個參數開始,表示是要傳遞給當前函數的參數。
var object = {
user: 'no.1',
};
function fn(arg1, arg2, arg3) {
console.log(
this,
arg1,
arg2,
arg3,
);
}
fn.call(object, 1, 2, 3);
apply 的第二個參數是個數組,數組里面的項會按數組的順序作為參數傳遞給函數。
var object = {
user: 'no.1',
};
function fn() {
console.log(
this,
arguments,
);
}
fn.apply(object, [1, 2, 3]);
通過 arguments
關鍵字就可以看到當前函數的參數,通常在需要修改 this ,又不確定參數的情況下,會使用 apply
來修改 this。
3.2 bind
bind 方法用于給一個函數永久綁定一個指定的 this,bind 不會修改原函數,會返回一個新的函數。
var obj1 = { value: '今天打磚' };
var obj2 = { value: '明天打轉' };
var fn = function() {
console.log(this);
};
var bindFn1 = fn.bind(obj1)
var bindFn2 = bindFn1.bind(obj2);
bindFn1();
bindFn2();
可以看到 bindFn1
被綁定了 obj1
作為 this,之后不論怎么操作,他的 this 都會是 obj1
。
bind 還有更多靈活的用法,參數也可以綁定,有關 bind、call、apply 這三個方法的更詳細的信息可以查閱對應的文檔。
4. 小結
理解好 this 的處理機制可以設計出更加完善的 JavaScript 應用程序。
this 在 ES6 的箭頭函數中的表現也有所不同,可以查閱 ES6 中有關箭頭函數的內容。