this 誤區(qū)
this 既不指向函數(shù)自身也不指向函數(shù)的詞法作用域。this 實際上是在函數(shù)被調(diào)用時發(fā)生的綁定,它指向什么完全取決于函數(shù)在哪里被調(diào)用。
調(diào)用位置
在理解 this 的綁定過程之前,首先要理解調(diào)用位置:調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)。
function baz() {
// 當(dāng)前調(diào)用棧是:baz
// 因此,當(dāng)前調(diào)用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的調(diào)用位置
}
function bar() {
// 當(dāng)前調(diào)用棧是 baz -> bar
// 因此,當(dāng)前調(diào)用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的調(diào)用位置
}
function foo() {
// 當(dāng)前調(diào)用棧是 baz -> bar -> foo
// 因此,當(dāng)前調(diào)用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的調(diào)用位置
四種綁定規(guī)則
- 默認(rèn)綁定
獨立函數(shù)內(nèi)調(diào)用??梢园堰@條規(guī)則看作是無法應(yīng)用其他規(guī)則時的默認(rèn)規(guī)則。
非嚴(yán)格模式
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
聲明在全局作用域中的變量就是全局對象的一個同名屬性。調(diào)用 foo() 時,this.a 被解析成了全局變量 a。
嚴(yán)格模式
"use strict";
function foo() {
console.log( this.b );
}
var b = 2;
foo(); // TypeError: this is undefined
如果使用嚴(yán)格模式(strict mode),那么全局對象將無法使用默認(rèn)綁定,因此 this 會綁定到 undefined。
總結(jié):this 的默認(rèn)綁定規(guī)則在非嚴(yán)格模式下綁定到全局對象,嚴(yán)格模式下綁定到 undefined。
- 隱式綁定
需要考慮的規(guī)則是調(diào)用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
foo 函數(shù)被當(dāng)作引用屬性添加到 obj 中,因此你可以說函數(shù)被調(diào)用時 obj 對象擁有或者包含它。當(dāng)函數(shù)引用有上下文對象時,隱式綁定規(guī)則會把函數(shù)調(diào)用中的 this 綁定到這個上下文對象。因為調(diào)用 foo() 時 this 被綁定到 obj,因此 this.a 和 obj.a 是一樣的。
對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 666,
foo: foo
};
var obj1 = {
a: 999,
obj2: obj2
};
obj1.obj2.foo(); // 666
隱式丟失情況
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數(shù)別名!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"
雖然 bar 是 obj.foo 的一個引用,但是實際上,它引用的是 foo 函數(shù)本身,因此此時的 bar() 其實是一個不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定。
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其實引用的是 foo
fn(); // <-- 調(diào)用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局對象的屬性
doFoo( obj.foo ); // "oops, global"
參數(shù)傳遞其實就是一種隱式賦值,因此我們傳入函數(shù)時也會被隱式賦值。
- 顯式綁定
JavaScript 提供的絕大多數(shù)函數(shù)以及你自己創(chuàng)建的所有函數(shù)都可以使用 call(..) ,apply(..) 和 bind(..) 方法,用來直接指定 this 的綁定對象。
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
通過 foo.call(..),我們可以在調(diào)用 foo 時強制把它的 this 綁定到 obj 上。
- new綁定
使用 new 來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時,會自動執(zhí)行下面的操作。
- 創(chuàng)建(或者說構(gòu)造)一個全新的對象。
- 這個新對象會被執(zhí)行[[原型]]連接。
- 這個新對象會綁定到函數(shù)調(diào)用的this。
- 如果函數(shù)沒有返回其他對象,那么 new 表達式中的函數(shù)調(diào)用會自動返回這個新對象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用 new 來調(diào)用 foo(..) 時,我們會構(gòu)造一個新對象并把它綁定到 foo(..) 調(diào)用中的 this 上。new 是最后一種可以影響函數(shù)調(diào)用時 this 綁定行為的方法,我們稱之為 new 綁定。
優(yōu)先級
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
顯式綁定優(yōu)先級高于隱式綁定。
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
new 綁定比隱式綁定優(yōu)先級高。
由于 new 和 call/apply 無法一起使用,因此我們不用關(guān)心他們優(yōu)先級關(guān)系。
best practice
如果要判斷一個運行中函數(shù)的 this 綁定,就需要找到這個函數(shù)的直接調(diào)用位置。找到之后
就可以順序應(yīng)用下面這四條規(guī)則來判斷 this 的綁定對象。
- 由 new 調(diào)用則綁定到新創(chuàng)建的對象。
- 由 cal l或者 apply 或者 bind 調(diào)用則綁定到指定的對象。
- 由上下文對象調(diào)用(隱式綁定)則綁定到那個上下文對象。
- 默認(rèn)綁定:在嚴(yán)格模式下綁定到 undefined,否則綁定到全局對象。
優(yōu)先級關(guān)系:
顯示綁定 > 隱式綁定 > 默認(rèn)綁定
new綁定 > 隱式綁定 > 默認(rèn)綁定
箭頭函數(shù)
ES6 中的箭頭函數(shù)并不會使用四條標(biāo)準(zhǔn)的綁定規(guī)則,而是根據(jù)當(dāng)前的詞法作用域來決定 this,具體來說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)。
function foo() {
// 返回一個箭頭函數(shù)
return (a) => {
//this 繼承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !
foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用時 foo() 的 this。由于 foo() 的 this 綁定到 obj1, bar(引用箭頭函數(shù))的 this 也會綁定到 obj1,箭頭函數(shù)的綁定無法被修改。
全局上下文
在全局執(zhí)行上下文中(在任何函數(shù)之外),this 指的是全局對象,無論它是否處于嚴(yán)格模式。
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
參考鏈接
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this