理解this
是咱們要深刻理解 JavaScript 中必不可少的一個步驟,同時只有理解了 this
,你才能更加清晰地寫出與本身預期一致的 JavaScript 代碼。javascript
本文是這系列的第三篇,往期文章:java
在解釋什麼是this
以前,須要先糾正大部分人對this
的誤解,常見的誤解有:數組
關於爲什麼會誤解的緣由這裏很少講,這裏只給出結論,有興趣能夠自行查詢資料。瀏覽器
this
在任何狀況下都不指向函數的詞法做用域。你不能使用 this
來引用一個詞法做用域內部的東西。閉包
排除了一些錯誤理解以後,咱們來看看 this
究竟是一種什麼樣的機制。app
this
是在運行時(runtime
)進行綁定的,而不是在編寫時綁定的,它的上下文(對象)取決於函數調用時的各類條件。this
的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。函數
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this
就是記錄的其中一個屬性,會在函數執行的過程當中用到。(PS:因此this
並不等價於執行上下文)工具
前面 咱們排除了一些對於 this
的錯誤理解而且明白了每一個函數的this
是在調用時被綁定的,徹底取決於函數的調用位置。post
一般來講,尋找調用位置就是尋找「函數被調用的位置「,其中最重要的是要分析調用棧(就是爲了到達當前執行位置所調用的全部函數)。咱們關心的調用位置就在當前正在執行的函數的前一個調用中。ui
下面咱們來看看到底什麼是調用棧和調用位置:
function foo(){
// 當前調用棧是:foo
// 所以,當前調用位置是全局做用域
console.log("foo");
bar(); // <-- bar的調用位置
}
function bar(){
// 當前調用棧是foo -> bar
console.log("bar");
}
foo(); // <-- foo 的調用位置
複製代碼
你能夠把調用棧想象成一個函數調用鏈, 就像咱們在前面代碼段的註釋中所寫的同樣。可是這種方法很是麻煩而且容易出錯。 另外一個查看調用棧的方法是使用瀏覽器的調試工具。 絕大多數現代桌面瀏覽器都內置了開發者工具,其中包含 JavaScript 調試器。
在找到調用位置後,則須要斷定代碼屬於下面四種綁定規則中的哪種,而後才能對this
進行綁定。 注意: this
綁定的是上下文對象,並非函數自身也不是函數的詞法做用域
這是最多見的函數調用類型:獨立函數調用:
對函數直接使用而不帶任何修飾的函數引用進行調用,簡單點一個函數直接是func()
這樣調用,不一樣於經過對象屬性調用例如obj.func()
,也沒有經過new關鍵字new Function()
,也沒有經過apply
、call
、bind
強制改變this
指向。
當被用做獨立函數調用時(不論這個函數在哪被調用,無論全局仍是其餘函數內),this
默認指向到Window
。(注意:在嚴格模式下this
再也不默認指向全局,而是undefined
)。
示例代碼:
function foo(){
console.log(this.name);
}
var name = "window";
foo(); // window
複製代碼
函數被某個對象擁有或者包含,也就是函數被做爲對象的屬性所引用,例如obj.func()
,此時this
會綁定到該對象上,這就是隱式綁定。
示例代碼:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
obj.foo(); // obj
複製代碼
隱式丟失:
大部分的this
綁定問題就是被「隱式綁定」的函數會丟失綁定對象,也就是說它會應用「默認綁定」,從而把this
綁定到Window
或undefined
上,這取決因而否是嚴格模式。
最多見的狀況就是把對象方法做爲回調函數進行傳遞時:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
var name = "window";
setTimeout(obj.foo,1000); // 一秒後輸出 window
複製代碼
咱們能夠經過apply
、call
、bind
方法來顯示地修改this
的指向。
關於這三個方法的定義(它們第一個參數都是接受this
的綁定對象):
apply
:調用函數,第二個參數傳入一個參數數組。call
:調用函數,其他參數正常傳遞。bind
:返回一個已經綁定this
的函數,其他參數正常傳遞。好比咱們可使用bind
方法解決上一節「隱式丟失」中的例子:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
var name = "window";
setTimeout(obj.foo.bind(obj),1000); // 一秒後輸出 obj
複製代碼
使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做:
this
。new
表達式中的函數調用會自動返回這個新對象。示例代碼:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
複製代碼
直接上結論:
new綁定=顯示綁定>隱式綁定>默認綁定
判斷this: 如今咱們能夠根據優先級來判斷函數在某個調用位置應用的是哪條規則。能夠按照下面的順序來進行判斷:
使用new綁定,this
綁定的是新建立的對象。
var bar = new foo();
複製代碼
經過call
之類的顯式綁定,this
綁定的是指定的對象。
var bar = foo.call(obj2);
複製代碼
在某個上下文對象中調用(隱式綁定),this 綁定的是那個上下文對象。
var bar = obj1.foo();
複製代碼
若是都不是的話,使用默認綁定。this
綁定到Window
或undefined
上,這取決因而否是嚴格模式。
var bar = foo();
複製代碼
對於正常的函數調用來講,理解了這些知識你就能夠明白 this 的綁定原理了。
ES6 中介紹了一種沒法使用上面四條規則的特殊函數類型:箭頭函數。
箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決定 this。(而傳統的this與函數做用域沒有任何關係,它只與調用位置的上下文對象有關)。
重要:
bind
同樣確保函數的this
被綁定到指定對象this
機制。示例代碼:
var obj = {
name : "obj",
foo : function(){
setTimeout(()=>{
console.log(console.log(this.name)); // obj
},1000);
}
}
obj.foo();
複製代碼
這在 ES6 以前是這樣解決的:
var obj = {
name : "obj",
foo : function(){
var self = this;
setTimeout(function(){
console.log(console.log(self.name)); // obj
},1000);
}
}
obj.foo();
複製代碼
總之若是要判斷一個運行中函數的this
綁定,就須要找到這個函數的直接調用位置。找到以後就能夠順序應用下面這四條規則來判斷this
的綁定對象。
undefined
,不然綁定到全局對象。ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法做用域來決定 this
,具體來講,箭頭函數會繼承外層函數調用的 this
綁定(不管 this
綁定到什麼)。這其實和 ES6 以前代碼中的 self = this
機制同樣。
注:此文爲原創文章,如需轉載,請註明出處。