前言 要正確理解this,首先得理解執行上下文,這裡推薦湯姆大叔的 "執行上下文" ,因為 是在運行代碼時確認具體指向誰,箭頭函數除外。 全局作用域中的this : 每個 文件都是一個模塊, 指向空對象( ) 當然也有些意外,比如下麵這種情況: 瀏覽器端 : 指向 。 函數作用域中的this 這裡分 ...
前言
要正確理解this,首先得理解執行上下文,這裡推薦湯姆大叔的執行上下文,因為this
是在運行代碼時確認具體指向誰,箭頭函數除外。
全局作用域中的this
node
: 每個javaScript
文件都是一個模塊,this
指向空對象(module.exports
)
this.a = 1;
console.log(this, module.exports);
// { a: 1 } { a: 1 }
當然也有些意外,比如下麵這種情況:
this.a = 1;
module.exports = {}
console.log(this, module.exports);
// { a: 1 } {}
瀏覽器端: this
指向window
。
函數作用域中的this
這裡分為兩種,一種是全局作用域下直接執行函數,另外一種是被當作某個對象的屬性的時候執行。eval的情況這裡不作討論。
全局環境下執行
function foo() {
console.log(this); // 此時的執行上下文為全局對象
}
foo();
// node global, 瀏覽器 window
當然嚴格模式下有不同,具體區別如下:
嚴格模式
this
指向undefined
(node
and 瀏覽器端)
非嚴格模式
瀏覽器端: this
指向全局變數window
node
: this
指向global
被當作屬性調用
當函數作為一個對象的屬性時,node
和瀏覽器端一致,指向調用該屬性的對象
var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
}
obj.foo();
// { name: 'foo', foo: [Function: foo] }
接下來,做一些升級。
var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
}
var objA = obj.foo;
objA();
// node環境指向global,瀏覽器端指向window,嚴格模式下均指向undefined
--------------------------------------------------------------
var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
}
var objA = {
name: 'objA',
foo: obj.foo
};
objA.foo();
// { name: 'objA', foo: [Function: foo] }
call、apply、bind
如果想手動更改函數里的this
指向,可通過上述3個方法。call
和apply
會立即執行,bind
則返回一個綁定好this
指向的函數。
var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
}
var objA = {
name: 'objA',
foo: obj.foo
};
obj.foo.call(objA); // 將this指向objA
obj.foo.apply(objA);
obj.foo.bind(objA)(); // bind函數會返回一個綁定好this的函數,可供以後調用
/**
{ name: 'objA', foo: [Function: foo] }
{ name: 'objA', foo: [Function: foo] }
{ name: 'objA', foo: [Function: foo] }
*/
這裡對上述3個方法進行更細的說明,方便更好的理解之間的差異。
var obj = {
name: 'foo',
foo: function foo() {
console.log(this, arguments); // 通過arguments對象訪問函數傳入的參數列表,類似數組但不是數組,可通過arguments[0]訪問到傳入的Tom
}
}
var objA = {
name: 'objA',
foo: obj.foo
};
obj.foo.call(objA, 'Tom', 'Jerry');
obj.foo.apply(objA, ['Tom', 'Jerry']);
obj.foo.bind(objA, 'Tom', 'Jerry')(1);
/**
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry' }
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry' }
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry', '2': 1 }
可以看到call和bind是按序列傳參,而apply是按數組傳參,bind不會更改傳參的順序
*/
new構造
當函數被當作構造函數調用時,this
指向構造的那個對象。
註:new
調用中的this
不會被call
、apply
、bind
改變。
接下來,簡單驗證一下,由於call
和apply
會立即執行,無法被當作構造函數,只能選擇bind
。
function Foo() {
console.log(this);
}
var foo = Foo.bind({ name: 'Tom' });
foo();
// { name: 'Tom' }
new foo();
// Foo {}
箭頭函數中的this
this
在定義時,就已經知道其具體指向,因為在運行到聲明的箭頭函數時,會將this
進行強綁定到外部作用域中的this
,且無法更改。可以理解為繼承了外部作用域中的this
。由於箭頭函數的this
是確定的,無法更改,因此也無法被當作構造函數調用。
外部作用域為全局作用域:
var foo = () => {
console.log(this);
}
this.a = 1;
foo();
// 或者下麵代碼
var obj = {
name: 'obj',
foo: () => {
console.log(this);
}
}
var foo = obj.foo;
obj.foo();
foo();
foo.call({ name: 'Tom' });
/**
因為obj是在全局作用域下被定義,所以外部作用域為全局對象
node: 指向module.exports
瀏覽器:指向window
*/
外部作用域為函數作用域:
function foo() {
var a = () => {
console.log(this); // 繼承外部作用域foo函數的this
};
a();
}
foo();
foo.call({ name: 'foo' });
new foo();
/**
這裡foo函數中的this並不確定,由於調用方式不同,其this指向也不同
*/
相信寫ES6類的情況很多,本人經常寫React
類組件,剛開始初學者會好奇為什麼在類組件里寫方法時要用bind
或者箭頭函數來強綁定this
。因為一般類組件里的方法,都會設計到this
的處理。比如事件處理函數,當觸發相應事件時,調用事件對應的處理函數,此時訪問到的this
為undefined
(ES6預設類與模塊內就是嚴格模式),這就導致不能正確處理該組件的狀態,甚至出錯(處理函數內可能調用this.setState
方法)。所以在類組件內部聲明方法時會需要我們進行強綁定。
接下來我們看看React
組件渲染流程:new
構造一個組件實例instance
,然後調用其render
方法進行渲染和事件綁定。new
構造的過程,this
已經確定指向構造的組件實例,所以你可以在constructor
進行bind
或直接使用箭頭函數,這樣函數內部this
就綁定到了instance
。render
函數里之所以能正常訪問this
,是因為以instance.render()
進行渲染。
當然這裡不特指React
類組件,只要是ES6類,只能用new
構造調用,否則會報錯,所以ES6類里this
指向是確定的,可以放心使用箭頭函數。
迷惑的代碼
還有一個比較迷惑的地方,遇到的機會很少,代碼如下:
(function(){
console.log(this); // 運行結果和全局作用域下執行結果一致
})();
// 由於沒有以對象屬性的方式調用,則被認為是全局環境下調用
--------------------------------------------------------
(function(){
console.log(this);
}).call({ name: 'Hello World' });
// { name: 'Hello World' },this指向可以被改變
--------------------------------------------------------
new (function(name){
this.name = name;
console.log(this);
})('Tom');
// { name: 'Tom' },this指向新創建的對象
更好的閱讀體驗在我的github,歡迎