前言
近期在翻《你不知道的Javacript》,讀來大有裨益,記憶短促,只有一邊記錄,一邊體會,才能融會貫通。同時,也向大家分享這些你知道或不知道的知識點,希望“你不知道的Javascript”,會成為我們都知道的Javascript。
簡介
本片內容節選和總結自書籍第二部分,this和物件原型一章。
預設繫結(this指向window)
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
複製程式碼
分析:先根據詞法靜態分析,foo所在作用域是全域性作用域,宣告foo函式會向window物件新增一個foo的屬性。呼叫foo方法相當於呼叫window.foo方法,所以此時foo中的this指向window物件,所以this.a的值即為外部變數a的值。
function foo() {
'use strict';
console.log(this.a);
}
var a = 2;
foo(); // TypeError
複製程式碼
But,如果在嚴格模式下執行程式碼,this並不會預設繫結到全域性物件上。
隱式繫結(繫結至呼叫物件)
funtion foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); //2
複製程式碼
分析:物件obj的foo屬性引用了foo函式,並且呼叫foo函式,是通過obj.foo來訪問的,因此foo中的this指向了呼叫它的物件obj。可以想象一下,在另一門xxxSript語言中,它所有的語法和語義和Javascript完全一致,唯獨它的全域性物件名稱叫“obj”。那麼你應該發現了,隱式繫結其實和上面的預設繫結是一樣的,只不過預設繫結有了window的預設語義。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
複製程式碼
由碼可見,在通過一系列物件引用呼叫函式時,函式中this僅僅繫結到它的直接呼叫物件上,簡單的講,誰牽著foo的小手,誰就是foo的小主子~。
隱式丟失(多級引用的迷惑)
function foo() {
console.log( this.a )
};
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo;
var a = "oops, global";
bar(); // "oops, global"
複製程式碼
不看答案,你有沒有覺得這塊程式碼會輸出2?先來看bar是如何被呼叫的。首先obj物件的foo屬性引用了foo函式,然後又將這個引用賦予了bar變數,最終我們呼叫了bar方法。我們知道js中物件和函式的賦值都是以引用的方式進行的,那麼這裡無論是obj.foo還是bar,都僅僅只是對foo函式的一個引用,在記憶體中的模樣,僅僅只是記錄了foo函式的記憶體地址。foo函式定義在全域性環境中,顯而易見,此處呼叫bar方法,也就是呼叫了window.foo函式。切記,不要被函式引用迷惑了雙眼~
顯式繫結
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
複製程式碼
Javascript中提供了兩個方法可以進行上下文(this)的顯式繫結,call和apply這兩個大名鼎鼎的方法想必大家都使用過或有所耳聞。在上述程式碼中,通過call方法呼叫了foo函式,並傳入了obj物件作為foo函式的上下文,那麼foo函式中的this也就指向了這個上下文環境的宿主,即obj物件。否則這裡就要輸出undefined了~
- 硬繫結
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // // 硬繫結的 bar 不可能再修改
bar.call( window ); //
複製程式碼
通過call或apply方法強制繫結上下文,並通過函式二次呼叫,便可以避免this的預設繫結和繫結丟失。foo的this指標被繫結到obj物件中,並通過匿名函式來呼叫,這樣無論如何都不會丟失obj的上下文環境了。
new繫結
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
複製程式碼
通過建構函式建立的物件,建構函式的this會自動指向這個生成物件。
優先順序
以上的幾種this繫結規則的優先順序,可以通過這些方法來判斷:
- 函式是否通過 new 呼叫(new 繫結)?如果是的話 this 繫結的是新建立的物件。 var bar = new foo()
- 函式是否通過 call、apply(顯式繫結)或者硬繫結呼叫?如果是的話,this 繫結的是 指定的物件。 var bar = foo.call(obj2)
- 函式是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,this 繫結的是那個上 下文物件。 var bar = obj1.foo()
- 如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到 undefined,否則繫結 到全域性物件。 var bar = foo()
總結
this實際是函式執行時,上下文環境的指標變數。通常情況下,找到函式的呼叫物件,再分析這個物件的上下文環境,便可以解決與this相關的問題。