在 JavaScript 中,this
的繫結規則有4種,規則間存在著不同的優先順序。
預設繫結
在非嚴格模式下,預設繫結會將 this
指向全域性物件。
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
複製程式碼
在嚴格模式下,預設繫結會將 this
指向 undefined
,此時對於上述程式碼,會報 TypeError: this is undefined
的型別錯誤。
隱式繫結
當函式被呼叫時,若函式引用具有上下文物件,則隱式繫結會將 this
繫結到這個上下文物件。
物件屬性引用鏈中,只有最頂層或者說最後一層會影響函式的呼叫位置,故此情況下,隱式繫結會將 `this 繫結到最後一層物件。
一個最常見的 this
繫結問題就是被隱式繫結的函式會丟失繫結物件,也就是說它會應用預設繫結,從而將 this
繫結到全域性物件或者 undefined
上,取決於是否是嚴格模式。
function foo() {
console.log(this.a);
}
var a = "Oops, global"
var obj2 = {
a: 2,
foo: foo
};
var obj1 = {
a: 42,
obj2: obj2
};
var bar = obj2.foo; //函式別名
obj2.foo(); // 42 - obj2是foo被呼叫時的上下文物件
obj1.obj2.foo(); // 42 - obj2是物件屬性引用鏈的最後一層
bar(); // "Oops, global" - 隱式丟失
複製程式碼
由於 bar
是 obj.foo
的一個引用,故在呼叫 bar
時,相當於不帶任何修飾的 foo
呼叫,故應用了預設繫結,即發生了隱式丟失。
一種更微妙、更常見且更出乎意料的隱式丟失情況,發生在傳入回撥函式時:
function foo() {
console.log(this.a);
}
function doFoo(fn) {
// fn是foo的引用
fn();
}
var obj = {
a: 2,
foo: foo
};
var a = "Oops, global";
doFoo(obj.foo); // "Oops, global"
複製程式碼
正如我們所見到的一樣,回撥函式丟失 this
繫結是非常常見的,除此之外,有種情況 this
的行為會出乎我們意料:呼叫回撥函式的函式可能會修改 this
。
顯式繫結
我們可以通過 apply
、call
和 bind
將函式中的 this
繫結到指定物件。
function foo() {
console.log(this.a);
}
var a = "Oops, global";
var obj = {
a: 2
};
foo.call(obj); // 2
複製程式碼
被忽略的 this
如果我們把 null
或 undefined
作為 this
的繫結物件傳入 call
、apply
或者是 bind
,這些值在呼叫時會被忽略,實際應用的是預設繫結規則。
一種非常常見的做法是使用 apply(..)
來“展開”一個陣列,並當作引數傳入一個函式。類似地,bind(..)
可以對引數進行柯里化(預先設定一些引數)。
function foo(a, b) {
console.log("a: " + a + ", b: " + b);
}
// 把陣列“展開”成引數
foo.apply(null, [2, 3]); // a: 2, b: 3
// 使用 bind(..) 進行柯里化
var bar = foo.bind(null, 2);
bar(3); // a: 2, b: 3
複製程式碼
在 ES6 中,可以用 ...
操作符代替 apply(..)
來“展開”陣列,foo(…[1, 2])
和 foo(1, 2)
是一樣的,這樣可以避免不必要的 this
繫結。
然而,總是使用 null
來忽略 this
繫結可能產生一些副作用,如果某個函式確實使用了 this
,那預設繫結規則就會把 this
繫結到全域性物件。
new 繫結
在 JavaScript 中,建構函式只是一些使用 new
操作符時被呼叫的函式,它們並不屬於某個類,也不會例項化一個類。實際上,它們甚至都不能說是一種特殊的函式型別,它們只是被 new
操作符呼叫的普通函式而已。
不存在所謂的“建構函式”,只有對函式的“構造呼叫”。
使用 new
來呼叫函式時,會自動執行以下操作:
- 建立一個全新的物件。
- 這個新物件會被執行 [[Prototype]] 連線。
- 這個新物件會繫結到函式呼叫的
this
。 - 如果函式沒有返回其他物件,那麼
new
表示式中的函式呼叫會自動返回這個新物件。
function foo1() {
this.a = 1;
}
function foo2() {
this.a = 2;
return {
a: 3
};
}
var a = new foo1();
var b = new foo2();
console.log(a); // {a: 1}
console.log(b); // {a: 3}
複製程式碼
箭頭函式
箭頭函式中的 this
只取決於它外面的第一個不是箭頭函式的函式的 this
。
function foo() {
return () => {
return () => {
console.log(this.a);
};
};
}
var a = 2;
console.log(foo()()()); // 2
複製程式碼
箭頭函式的 this
一旦繫結了上下文,就不會被任何程式碼改變。
判斷 this 指向
- 函式是否在
new
中呼叫(new 繫結)?如果是的話,this
繫結的是新建立的物件。 - 函式是否通過
call
、apply
或bind
顯示繫結?如果是的話,this
繫結的是指定的物件。 - 函式是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,
this
繫結的是那個上下文物件。 - 如果都不是的話,則使用預設繫結。如果在嚴格模式,則繫結到
undefined
,否則繫結到全域性物件。