Javascript Reference Type

2023-02-17 10:53 更新

深入的語言特性

本文所講的是一個(gè)高階主題,能幫你更好地理解一些邊緣情況。

這僅是錦上添花。許多經(jīng)驗(yàn)豐富的開發(fā)者不甚了了也過得不錯(cuò)。如果你想了解代碼運(yùn)行的本質(zhì),那就繼續(xù)讀下去吧。

一個(gè)動態(tài)執(zhí)行的方法調(diào)用可能會丟失 this

例如:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // 正常運(yùn)行

// 現(xiàn)在讓我們基于 name 來選擇調(diào)用 user.hi 或 user.bye
(user.name == "John" ? user.hi : user.bye)(); // Error!

在最后一行有個(gè)在 user.hi 和 user.bye 中做選擇的條件(三元)運(yùn)算符。當(dāng)前情形下的結(jié)果是 user.hi。

接著該方法被通過 () 立刻調(diào)用。但是并不能正常工作!

如你所見,此處調(diào)用導(dǎo)致了一個(gè)錯(cuò)誤,因?yàn)樵谠撜{(diào)用中 "this" 的值變成了 undefined。

這樣是能工作的(對象.方法):

user.hi();

這就無法工作了(被評估的方法):

(user.name == "John" ? user.hi : user.bye)(); // Error!

為什么呢?欲知緣何,且讓我們深入 obj.method() 調(diào)用運(yùn)行的本質(zhì)。

Reference type 解讀

仔細(xì)看的話,我們可能注意到 obj.method() 語句中的兩個(gè)操作:

  1. 首先,點(diǎn) ?'.'? 取了屬性 ?obj.method? 的值。
  2. 接著 ?()? 執(zhí)行了它。

那么,this 的信息是怎么從第一部分傳遞到第二部分的呢?

如果我們將這些操作放在不同的行,this 必定是會丟失的:

let user = {
  name: "John",
  hi() { alert(this.name); }
};

// 把獲取方法和調(diào)用方法拆成兩行
let hi = user.hi;
hi(); // 報(bào)錯(cuò)了,因?yàn)?this 的值是 undefined

這里 hi = user.hi 把函數(shù)賦值給了一個(gè)變量,接下來在最后一行它是完全獨(dú)立的,所以這里沒有 this。

為確保 user.hi() 調(diào)用正常運(yùn)行,JavaScript 玩了個(gè)小把戲 —— 點(diǎn) '.' 返回的不是一個(gè)函數(shù),而是一個(gè)特殊的 Reference Type 的值。

Reference Type 是 ECMA 中的一個(gè)“規(guī)范類型”。我們不能直接使用它,但它被用在 JavaScript 語言內(nèi)部。

Reference Type 的值是一個(gè)三個(gè)值的組合 (base, name, strict),其中:

  • ?base? 是對象。
  • ?name? 是屬性名。
  • ?strict? 在 ?use strict? 模式下為 true。

對屬性 user.hi 訪問的結(jié)果不是一個(gè)函數(shù),而是一個(gè) Reference Type 的值。對于 user.hi,在嚴(yán)格模式下是:

// Reference Type 的值
(user, "hi", true)

當(dāng) () 被在 Reference Type 上調(diào)用時(shí),它們會接收到關(guān)于對象和對象的方法的完整信息,然后可以設(shè)置正確的 this(在此處 =user)。

Reference Type 是一個(gè)特殊的“中間人”內(nèi)部類型,目的是從 . 傳遞信息給 () 調(diào)用。

任何例如賦值 hi = user.hi 等其他的操作,都會將 Reference Type 作為一個(gè)整體丟棄掉,而會取 user.hi(一個(gè)函數(shù))的值并繼續(xù)傳遞。所以任何后續(xù)操作都“丟失”了 this。

因此,this 的值僅在函數(shù)直接被通過點(diǎn)符號 obj.method() 或方括號 obj['method']() 語法(此處它們作用相同)調(diào)用時(shí)才被正確傳遞。還有很多種解決這個(gè)問題的方式,例如 func.bind()。

總結(jié)

Reference Type 是語言內(nèi)部的一個(gè)類型。

讀取一個(gè)屬性,例如在 obj.method() 中,. 返回的準(zhǔn)確來說不是屬性的值,而是一個(gè)特殊的 “Reference Type” 值,其中儲存著屬性的值和它的來源對象。

這是為了隨后的方法調(diào)用 () 獲取來源對象,然后將 this 設(shè)為它。

對于所有其它操作,Reference Type 會自動變成屬性的值(在我們這個(gè)情況下是一個(gè)函數(shù))。

這整個(gè)機(jī)制對我們是不可見的。它僅在一些微妙的情況下才重要,例如使用表達(dá)式從對象動態(tài)地獲取一個(gè)方法時(shí)。

任務(wù)


檢查語法

重要程度: 2

這段代碼的結(jié)果是什么?

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)()

提示:有一個(gè)陷阱哦 :)


解決方案

錯(cuò)誤!

試一下:

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)() // error!

大多數(shù)瀏覽器中的錯(cuò)誤信息并不能說明是什么出現(xiàn)了問題。

出現(xiàn)此錯(cuò)誤是因?yàn)樵?nbsp;user = {...} 后面漏了一個(gè)分號。

JavaScript 不會在括號 (user.go)() 前自動插入分號,所以解析的代碼如下:

let user = { go:... }(user.go)()

然后我們還可以看到,這樣的聯(lián)合表達(dá)式在語法上是將對象 { go: ... } 作為參數(shù)為 (user.go) 的函數(shù)。這發(fā)生在 let user 的同一行上,因此 user 對象是甚至還沒有被定義,因此出現(xiàn)了錯(cuò)誤。

如果我們插入該分號,一切都變得正常:

let user = {
  name: "John",
  go: function() { alert(this.name) }
};

(user.go)() // John

要注意的是,(user.go) 外邊這層括號在這沒有任何作用。通常用它們來設(shè)置操作的順序,但在這里點(diǎn)符號 . 總是會先執(zhí)行,所以并沒有什么影響。分號是唯一重要的。


解釋 "this" 的值

重要程度: 3

在下面的代碼中,我們試圖連續(xù)調(diào)用 obj.go() 方法 4 次。

但是前兩次和后兩次調(diào)用的結(jié)果不同,為什么呢?

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [object Object]

(obj.go)();             // (2) [object Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

解決方案

這里是解析。

  1. 它是一個(gè)常規(guī)的方法調(diào)用。
  2. 同樣,括號沒有改變執(zhí)行的順序,點(diǎn)符號總是先執(zhí)行。
  3. 這里我們有一個(gè)更復(fù)雜的 ?(expression)()? 調(diào)用。這個(gè)調(diào)用就像被分成了兩行(代碼)一樣:
  4. f = obj.go; // 計(jì)算函數(shù)表達(dá)式
    f();        // 調(diào)用

    這里的 f() 是作為一個(gè)沒有(設(shè)定)this 的函數(shù)執(zhí)行的。

  5. 與 ?(3)? 相類似,在括號 ?()? 的左邊也有一個(gè)表達(dá)式。

要解釋 (3) 和 (4) 得到這種結(jié)果的原因,我們需要回顧一下屬性訪問器(點(diǎn)符號或方括號)返回的是引用類型的值。

除了方法調(diào)用之外的任何操作(如賦值 = 或 ||),都會把它轉(zhuǎn)換為一個(gè)不包含允許設(shè)置 this 信息的普通值。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號