JavaScript this 繫結規則

Jiahonzheng發表於2018-04-01

在 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" - 隱式丟失
複製程式碼

由於 barobj.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

顯式繫結

我們可以通過 applycallbind 將函式中的 this 繫結到指定物件。

function foo() {
    console.log(this.a);
}
var a = "Oops, global";
var obj = {
    a: 2
};

foo.call(obj); // 2
複製程式碼

被忽略的 this

如果我們把 nullundefined 作為 this 的繫結物件傳入 callapply 或者是 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 指向

  1. 函式是否在 new 中呼叫(new 繫結)?如果是的話,this 繫結的是新建立的物件。
  2. 函式是否通過 callapplybind 顯示繫結?如果是的話,this 繫結的是指定的物件。
  3. 函式是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,this 繫結的是那個上下文物件。
  4. 如果都不是的話,則使用預設繫結。如果在嚴格模式,則繫結到 undefined ,否則繫結到全域性物件。

相關文章