我們都會知道的Javascript:this的繫結規則

Zwe1發表於2019-03-18

前言

近期在翻《你不知道的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了~

  1. 硬繫結
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繫結規則的優先順序,可以通過這些方法來判斷:

  1. 函式是否通過 new 呼叫(new 繫結)?如果是的話 this 繫結的是新建立的物件。 var bar = new foo()
  2. 函式是否通過 call、apply(顯式繫結)或者硬繫結呼叫?如果是的話,this 繫結的是 指定的物件。 var bar = foo.call(obj2)
  3. 函式是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,this 繫結的是那個上 下文物件。 var bar = obj1.foo()
  4. 如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到 undefined,否則繫結 到全域性物件。 var bar = foo()

總結

this實際是函式執行時,上下文環境的指標變數。通常情況下,找到函式的呼叫物件,再分析這個物件的上下文環境,便可以解決與this相關的問題。

相關文章