JS this和呼叫物件

2020-07-16 10:05:07
JavaScript 函數的作用域是靜態的,但是函數的呼叫卻是動態的。由於函數可以在不同的執行環境內執行,因此 JavaScript 在函數體內定義了 this 關鍵字,用來獲取當前的執行環境。

this 是一個指標型變數,它動態參照當前的執行環境。具體來說,就是呼叫函數的物件。呼叫物件是可以存取的 JavaScript 物件,而執行上下文的變數物件是一個不可存取的抽象概念。同時,在一個執行上下文中會存在多個呼叫函數的物件,但是只有一個變數物件。

範例1

下面範例在全域性上下文中宣告一個變數 x,初始化值為 1。然後在 obj 物件內定義一個屬性 x,初始化值為 2。使用函數 f 檢測不同執行環境下 x 值的變化,以此檢測 this 指標的參照物件。
var x = 1;  //宣告全域性變數並初始化
var obj = {
    f : function () {  //定義方法f
        console.log(this.x);  //存取當前執行環境中x的屬性值
    },
    x : 2,  //定義屬性x,賦值為2
};
//obj環境執行
obj.f();  //2
var f1 = obj.f;
//window環境執行
f1();  //1
在上面程式碼中,obj.f() 表示在 obj 物件上呼叫 f 函數,則呼叫物件為 obj,此時 this 就指向 obj,this.x 就等於 obj.x,即返回結果為 2。若把 obj.f 賦值給變數 f1,然後在全域性上下文中呼叫 f1 函數,則 f 函數體的執行環境在全域性上下文中執行,此時 this 就指向 window,this.x 就等於 window.x,即返回結果為 1。

範例2

this 總是指代函數的當前執行環境。針對範例 1 的程式碼,如果使用下面 3 種方式呼叫 f 函數,會發現返回值都是 1。
var x = 1;  //宣告全域性變數並初始化
var obj = {
    f : function () {  //定義方法f
        console.log(this.x);  //存取當前執行環境中x的屬性值
    },
    x : 2,  //定義屬性x,賦值為2
};
(obj.f = obj.f) ();  //1
(false || obj.f) ();  //1
(obj.f, obj.f) ();  //1
在上面程式碼中,小括號左側都是一個表示式,表示式的值都是 obj.f,而在範例 1 中可以看到使用 obj.f() 呼叫函數 f,返回值是 2。為什麼現在換一種表示方法返回值都是 1 呢?

問題的關鍵是如何正確理解“執行環境”。上面 3 種表示式中,obj.f = obj.f 是賦值表示式,把 obj.f 賦值給 obj.f,obj.f 是一個地址,把地址賦值給 obj.f 屬性,表示式的執行環境發生在全域性上下文中,所以此時函數 f 內的 this 就指向了全域性上下文的呼叫物件 window。

false || obj.f 是一個邏輯表示式,左側運算元為 false,則運算右側運算元,返回 obj.f 的值,即參照地址。由於這個邏輯表示式運算發生在全域性作用域內,此時的 f 函數內 this 就指向了全域性物件。

obj.f,obj.f 是一個逗號運算表示式,逗號左側和右側的 obj.f 都是一個地址,都被運算一次,最後返回第 2 個運算元的值,即返回參照地址。由於這個操作發生在全域性作用域內,所以 f 函數內 this 也指向了全域性物件。

但是,對於下面形式的呼叫,不會返回 1,而是返回 2,即 this 指向 obj 物件。因為小括號不是一個運算子,它僅是一個邏輯分隔符,不執行運算,不會產生執行環境。當使用小括號呼叫函數時,此時發生的執行環境就是 obj 了。
(obj.f) ();  //2