this全面解析(二)

legendaryedu發表於2019-04-08

上篇文章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繫結兩種方式。

相關文章