每個函數的this是在調用時被綁定的,完全取決於函數的調用位置;
什麼是調用位置呢?
顧名思義就是“函數被調用的位置”;要去尋找被調用的位置,那麼就要分析調用棧,this的調用位置就在當前正在執行的函數的前一個調用中;
舉個例子
function baz() {
// 當前調用棧是全局 -> baz,調用位置是上一個執行棧就是全局作用域
bar() // bar的調用位置
}
function bar() {
// 當前調用棧是全局 -> baz -> bar,調用位置是上一個執行棧就是baz
foo() // foo的調用位置
}
function foo() {
// 當前調用棧是全局 -> baz -> bar -> foo,調用位置是上一個執行棧就是bar
}
baz() // baz的調用位置
接下來我們看看再函數的執行過程中調用位置是如何決定this的綁定對象的。
this綁定有四條規則,分別是:
a.默認綁定
b.隱式綁定
c.顯式綁定
d.new綁定
四種規則分別從弱到強,new綁定優先級最高,依次類推;
默認綁定
可以把這條規則是無法應用其他規則時的默認規則;
function foo() {
// this指向全局
console.log(this.a)
}
a = 1;
foo(); // 輸出1
這裏由於foo函數時直接引用進行調用的,只能使用默認綁定,無法應用其他規則,所以this指向全局對象,而全局變量a爲1,所以輸出1;
需要注意的是:再嚴格模式下,this會綁定到undefined,而不是全局對象;
隱式綁定
調用位置是否有上下文對象是隱式綁定需要考慮的規則,或者說是否被某個對象引用;
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
obj.foo(); // 輸出1
可以看到,obj對象的foo屬性添加了foo函數的引用,當foo被調用時,指向了obj對象,當函數引用有上下文對象時,隱式綁定規則會把this綁定到這個上下文對象,即obj對象;
注意:對象屬性引用鏈中只有最後一層會影響調用位置,舉個例子:
function foo() {
console.log(this.a)
}
var obj1 = {
a: 1,
obj2: obj2
}
var obj2 = {
a: 2,
foo: foo
}
obj1.obj2.foo(); // 輸出2
顯式綁定
其實這種方法很常見,就是通過call/apply/bind等強綁定,可以直接指定this的綁定對象。這裏就不展開描述了
new綁定
相信大家對new構造函數調用都耳熟能詳,使用new調用函數,對自動執行下面的操作:
a.創建一個全新的對象;
b.這個新對象會被執行原型連接;
c.這個新對象會被綁定到函數調用的this;
d.如果函數沒有返回其他對象,那麼new表達式中的函數調用會自動返回這個新對象;
看以下代碼
function foo() {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
使用new來調用foo()時,會構造一個新對象並把它綁定到foo()調用中的this上。
接下來總結一下如何判斷this
1、函數是否再new中調用?是的話this綁定的是新創建的對象;
2、函數是否通過call/apply調用?是的話this綁定的是指定的對象;
3、函數是否在某個上下文對象中調用?是的話this綁定的是那個上下文對象;
4、如果都不是以上,使用默認綁定,如果在嚴格模式下,被綁定到undefined,否則就綁定到全局對象;
以上就是this的綁定原理啦~
ES6 中介紹了一種無法使用這些規則的特殊函數類型:箭頭函數
箭頭函數並不會使用this的四條綁定規則,而是根據當前的詞法作用域來決定this,具體來說,箭頭函數會繼承外層函數調用的this綁定(無論this綁定到什麼),這和之前代碼中的self = this機制一樣;