W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
深入的語言特性
本文所講的是一個高階主題,能幫你更好地理解一些邊緣情況。
這僅是錦上添花。許多經驗豐富的開發(fā)者不甚了了也過得不錯。如果你想了解代碼運行的本質,那就繼續(xù)讀下去吧。
一個動態(tài)執(zhí)行的方法調用可能會丟失 this
。
例如:
let user = {
name: "John",
hi() { alert(this.name); },
bye() { alert("Bye"); }
};
user.hi(); // 正常運行
// 現在讓我們基于 name 來選擇調用 user.hi 或 user.bye
(user.name == "John" ? user.hi : user.bye)(); // Error!
在最后一行有個在 user.hi
和 user.bye
中做選擇的條件(三元)運算符。當前情形下的結果是 user.hi
。
接著該方法被通過 ()
立刻調用。但是并不能正常工作!
如你所見,此處調用導致了一個錯誤,因為在該調用中 "this"
的值變成了 undefined
。
這樣是能工作的(對象.方法):
user.hi();
這就無法工作了(被評估的方法):
(user.name == "John" ? user.hi : user.bye)(); // Error!
為什么呢?欲知緣何,且讓我們深入 obj.method()
調用運行的本質。
仔細看的話,我們可能注意到 obj.method()
語句中的兩個操作:
'.'
? 取了屬性 ?obj.method
? 的值。()
? 執(zhí)行了它。那么,this
的信息是怎么從第一部分傳遞到第二部分的呢?
如果我們將這些操作放在不同的行,this
必定是會丟失的:
let user = {
name: "John",
hi() { alert(this.name); }
};
// 把獲取方法和調用方法拆成兩行
let hi = user.hi;
hi(); // 報錯了,因為 this 的值是 undefined
這里 hi = user.hi
把函數賦值給了一個變量,接下來在最后一行它是完全獨立的,所以這里沒有 this
。
為確保 user.hi()
調用正常運行,JavaScript 玩了個小把戲 —— 點 '.'
返回的不是一個函數,而是一個特殊的 Reference Type 的值。
Reference Type 是 ECMA 中的一個“規(guī)范類型”。我們不能直接使用它,但它被用在 JavaScript 語言內部。
Reference Type 的值是一個三個值的組合 (base, name, strict)
,其中:
base
? 是對象。name
? 是屬性名。strict
? 在 ?use strict
? 模式下為 true。對屬性 user.hi
訪問的結果不是一個函數,而是一個 Reference Type 的值。對于 user.hi
,在嚴格模式下是:
// Reference Type 的值
(user, "hi", true)
當 ()
被在 Reference Type 上調用時,它們會接收到關于對象和對象的方法的完整信息,然后可以設置正確的 this
(在此處 =user
)。
Reference Type 是一個特殊的“中間人”內部類型,目的是從 .
傳遞信息給 ()
調用。
任何例如賦值 hi = user.hi
等其他的操作,都會將 Reference Type 作為一個整體丟棄掉,而會取 user.hi
(一個函數)的值并繼續(xù)傳遞。所以任何后續(xù)操作都“丟失”了 this
。
因此,this
的值僅在函數直接被通過點符號 obj.method()
或方括號 obj['method']()
語法(此處它們作用相同)調用時才被正確傳遞。還有很多種解決這個問題的方式,例如 func.bind()。
Reference Type 是語言內部的一個類型。
讀取一個屬性,例如在 obj.method()
中,.
返回的準確來說不是屬性的值,而是一個特殊的 “Reference Type” 值,其中儲存著屬性的值和它的來源對象。
這是為了隨后的方法調用 ()
獲取來源對象,然后將 this
設為它。
對于所有其它操作,Reference Type 會自動變成屬性的值(在我們這個情況下是一個函數)。
這整個機制對我們是不可見的。它僅在一些微妙的情況下才重要,例如使用表達式從對象動態(tài)地獲取一個方法時。
這段代碼的結果是什么?
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)()
提示:有一個陷阱哦 :)
錯誤!
試一下:
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)() // error!
大多數瀏覽器中的錯誤信息并不能說明是什么出現了問題。
出現此錯誤是因為在 user = {...}
后面漏了一個分號。
JavaScript 不會在括號 (user.go)()
前自動插入分號,所以解析的代碼如下:
let user = { go:... }(user.go)()
然后我們還可以看到,這樣的聯(lián)合表達式在語法上是將對象 { go: ... }
作為參數為 (user.go)
的函數。這發(fā)生在 let user
的同一行上,因此 user
對象是甚至還沒有被定義,因此出現了錯誤。
如果我們插入該分號,一切都變得正常:
let user = {
name: "John",
go: function() { alert(this.name) }
};
(user.go)() // John
要注意的是,(user.go)
外邊這層括號在這沒有任何作用。通常用它們來設置操作的順序,但在這里點符號 .
總是會先執(zhí)行,所以并沒有什么影響。分號是唯一重要的。
在下面的代碼中,我們試圖連續(xù)調用 obj.go()
方法 4 次。
但是前兩次和后兩次調用的結果不同,為什么呢?
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
這里是解析。
(expression)()
? 調用。這個調用就像被分成了兩行(代碼)一樣:f = obj.go; // 計算函數表達式
f(); // 調用
這里的 f()
是作為一個沒有(設定)this
的函數執(zhí)行的。
(3)
? 相類似,在括號 ?()
? 的左邊也有一個表達式。要解釋 (3)
和 (4)
得到這種結果的原因,我們需要回顧一下屬性訪問器(點符號或方括號)返回的是引用類型的值。
除了方法調用之外的任何操作(如賦值 =
或 ||
),都會把它轉換為一個不包含允許設置 this
信息的普通值。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: