上篇文章this全面解析(一)中,我們說了幾個常見的錯誤認識。
接下來,我們來看看this到底是一種什麼樣的機制
this是執行時繫結,並不是在編譯時,上下文取決於函式呼叫時的各種條件。this繫結和函式的宣告位置沒有任何關係,只取決於函式的呼叫方式。
呼叫位置
呼叫位置是函式在程式碼中被呼叫的位置(不是宣告的位置)。
一般來說,尋找呼叫位置就是找“函式被呼叫的位置”,但這並不一直是一件簡單事,有些時候,程式設計模式會隱藏真正的呼叫位置。
不過我們還是有辦法的!我們可以通過分析呼叫棧(為了達到當前執行位置所呼叫的所有函式),那麼我們關心的,就是當前執行函式的前一個呼叫中。
function baz() {
//當前呼叫棧:baz
//呼叫位置是全域性作用域
console.log('baz');
bar(); //bar 的呼叫位置
}
function bar() {
//當前呼叫棧 baz--> bar
//因此當前呼叫位置在 baz 中
console.log('bar');
foo(); // foo的呼叫位置
}
function foo() {
//當前呼叫棧 baz-> bar ->foo
//當前呼叫位置在 bar 中
console.log('foo');
}
baz(); //baz呼叫位置
複製程式碼
其實,函式的呼叫棧也可以被想象成一個函式呼叫鏈~
繫結規則
預設繫結
獨立函式呼叫(無法應用其他規則時,預設規則就是它了!)
看這段程式碼
function foo() {
console.log(this.a);
}
var a = 1;
foo(); //2
複製程式碼
首先,我們需要明白一個知識點:宣告在全域性作用域中的變數就是全域性物件的一個同名屬性,本質是一個東西,不是複製得到的
當我們呼叫foo,this.a 被解析成 全域性變數a ,呼叫函式應用了this的預設繫結,this指向全域性物件。
不過這裡有一點
雖然this的繫結規則完全取決於呼叫位置,但是隻有 foo() 執行在 非嚴格模式下,預設繫結才會繫結到全域性,嚴格模式下,與foo()的呼叫位置無關。
隱式繫結
考慮呼叫位置,是否有上下文物件。
function foo(){
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); //2
複製程式碼
我們先來看 foo() 的宣告方式,無論是直接在obj中定義還是先定義,再新增為引用屬性,這個函式嚴格來說都不屬於obj物件。
但是,呼叫位置會使用obj上下文來引用函式,所以,你可以說,呼叫obj物件時,擁有或者包含它。呼叫foo()時,this被繫結到obj。this.a 和 obj.a 是一樣的。
物件屬性引用鏈中,只有最後一層影響呼叫位置
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo(); //42
複製程式碼
隱式丟失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函式別名!
var a = "oops, global"; // a 是全域性物件的屬性 bar(); // "oops, global"
複製程式碼
雖然,bar是obj.foo的一個引用,實際上,引用的是foo函式本身!所以此時 bar() 是一個不帶任何修飾的函式呼叫。
傳入回撥函式
function foo(){
console.log( this.a );
}
function doFoo(fn) {
fn(); //呼叫位置
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
doFoo( obj.foo ); //global
複製程式碼
引數傳遞其實就是一種隱式賦值,因此我們傳入函式時也會被隱式賦值,所以結果和上一 個例子一樣。
下篇文章將介紹:顯式繫結和new繫結兩種方式。