從 JavaScript 執行上下文的視角講清楚 this

語言: CN / TW / HK

theme: devui-blue highlight: a11y-dark


我正在參加「掘金·啟航計劃」

前言

在物件內部的方法中使用物件內部的屬性是一個非常普遍的需求。但是 JavaScript 的作用域機制並不支援這一點,基於這個需求,JavaScript 有另外一套 this 機制。

this 是和執行上下文繫結的,也就是說每個執行上下文中都有一個 this。執行上下文主要分為三種—— 全域性執行上下文函式執行上下文eval 執行上下文,所以對應的 this 也只有這三種——全域性執行上下文中的 this、函式中的 this 和 eval 中的 this。

全域性執行上下文中的 this

全域性執行上下文中的 this 是指向 window 物件的。這也是 this 和作用域鏈的 唯一交點,作用域鏈的最底端包含了 window 物件,全域性執行上下文中的 this 也是指向 window 物件。

函式執行上下文中的 this

js function foo() { console.log(this); } foo();

執行這段程式碼,打印出來的也是 window 物件,這說明在預設情況下呼叫一個函式,其執行上下文中的 this 也是指向 window 物件的。

通常情況下,有下面三種方式來設定函式執行上下文中的 this 值:

1. 通過函式的 call 方法設定

js let bar = { myName: "極客邦", test1: 1, }; function foo() { this.myName = "極客時間"; } foo.call(bar); console.log(bar); console.log(myName);

執行這段程式碼,你就能發現 foo 函式內部的 this 已經指向了 bar 物件,因為通過列印 bar 物件,可以看出 barmyName 屬性已經由“極客邦”變為“極客時間”了,同時在全域性執行上下文中列印 myName,JavaScript 引擎提示該變數未定義。其實除了 call 方法,你還可以使用 bindapply 方法來設定函式執行上下文中的 this

2. 通過物件呼叫方法設定

js var myObj = { name: "極客時間", showThis: function () { console.log(this); }, }; myObj.showThis();

執行這段程式碼,你可以看到,最終輸出的 this 值是指向 myObj 的。所以,你可以得出這樣的結論:使用物件來呼叫其內部的一個方法,該方法的 this 是指向物件本身的。

接下來我們稍微改變下呼叫方式,把 showThis 賦給一個全域性物件,然後再呼叫該物件,程式碼如下所示:

js var myObj = { name: "極客時間", showThis: function () { this.name = "極客邦"; console.log(this); }, }; var foo = myObj.showThis; foo();

執行這段程式碼,你會發現 this 又指向了全域性 window 物件。

結論:

  1. 在全域性環境中呼叫一個函式,函式內部的 this 指向的是全域性變數 window

  2. 通過一個物件來呼叫其內部的一個方法,該方法的執行上下文中的 this 指向物件本身。

3. 通過建構函式中設定

js function CreateObj() { this.name = "極客時間"; } var myObj = new CreateObj();

當執行 new CreateObj() 的時候,JavaScript 引擎做了如下四件事:

  1. 首先建立了一個空物件 tempObj

  2. 接著呼叫 CreateObj.call 方法,並將 tempObj 作為 call 方法的引數,這樣當 CreateObj 的執行上下文建立時,它的 this 就指向了 tempObj 物件

  3. 然後執行 CreateObj 函式,此時的 CreateObj 函式執行上下文中的 this 指向了 tempObj 物件

  4. 最後返回 tempObj 物件

js var tempObj = {}; CreateObj.call(tempObj); return tempObj;

這樣,就通過 new 關鍵字構建好了一個新物件,並且建構函式中的 this 其實就是新物件本身。

this 的設計缺陷以及應對方案

1. 巢狀函式中的 this 不會從外層函式中繼承

js var myObj = { name: "極客時間", showThis: function () { console.log(this); function bar() { console.log(this); } bar(); }, }; myObj.showThis();

執行這段程式碼後,會發現函式 bar 中的 this 指向的是全域性 window 物件,而函式 showThis 中的 this 指向的是 myObj 物件。

可以通過一個小技巧來解決這個問題,比如在 showThis 函式中宣告一個變數 that 用來儲存 this,然後在 bar 函式中使用 that。其實,這個方法的的本質是把 this 體系轉換為了作用域的體系。

其實,你也可以使用 ES6 中的箭頭函式來解決這個問題:

js var myObj = { name: "極客時間", showThis: function () { console.log(this); var bar = () => { console.log(this); }; bar(); }, }; myObj.showThis();

這是因為 ES6 中的箭頭函式並不會建立其自身的執行上下文,所以箭頭函式中的 this 取決於它的外部函式。

2. 普通函式中的 this 預設指向全域性物件 window

在實際工作中,我們並不希望函式執行上下文中的 this 預設指向全域性物件,因為這樣會打破資料的邊界,造成一些誤操作。如果要讓函式執行上下文中的 this 指向某個物件,最好的方式是通過 call 方法來顯示呼叫。

可以通過設定 JavaScript 的 嚴格模式 來解決(在第一行加上 "use strict";)。在嚴格模式下,預設執行一個函式,其函式的執行上下文中的 this 值是 undefined