摘要:另一方面,我不建議初次接觸的開發(fā)人員閱讀規(guī)范。在維護(hù)語言的最新規(guī)范。在這一點(diǎn)上,我想指出的是,絕對沒有人從上到下閱讀規(guī)范。拓展閱讀由于的定義,中的細(xì)節(jié)如冒泡錯(cuò)誤,直到塊在規(guī)范中不存在。換句話說,會(huì)轉(zhuǎn)發(fā)中拋出的錯(cuò)誤,并終止其余的步驟。
翻譯自:How to Read the ECMAScript Specification
Ecmascript 語言規(guī)范 The ECMAScript Language specification(又名:Javascript 規(guī)范 the JavaScript specification 或 ECMA-262)是學(xué)習(xí) JavaScript 底層工作原理的非常好的資源。 然而,這是一個(gè)龐大的專業(yè)文本資料,咋一眼看過去,大家可能會(huì)感到迷茫、恐懼,滿懷激情卻無從下手。
前言不管你是打算每天閱讀一點(diǎn) ECMAScript 規(guī)范,還是把它當(dāng)成一個(gè)年度或者季度的目標(biāo),這篇文章旨在讓你更輕松的開始閱讀最權(quán)威的 JavaScript 語言參考資料。
為什么要閱讀 ECMAScript 規(guī)范Ecmascript 規(guī)范是所有 JavaScript 運(yùn)行行為的權(quán)威來源,無論是在你的瀏覽器環(huán)境,還是在服務(wù)器環(huán)境( Node.js ),還是在宇航服上[ NodeJS-NASA ] ,或在你的物聯(lián)網(wǎng)設(shè)備上[ JOHNNY-FIVE ]。 所有 JavaScript 引擎的開發(fā)者都依賴于這個(gè)規(guī)范來確保他們各種天花亂墜的新特性能夠其他 JavaScript 引擎一樣,按預(yù)期工作。
Ecmascript 規(guī)范 絕不僅僅對 JavaScript 引擎開發(fā)者有用,它對普通的 JavaScript 編碼人員也非常有用,而你只是沒有意識(shí)到或者沒有用到。
假設(shè)有一天你在工作中發(fā)現(xiàn)了下面這個(gè)奇怪的問題:
> Array.prototype.push(42) 1 > Array.prototype [ 42 ] > Array.isArray(Array.prototype) true > Set.prototype.add(42) TypeError: Method Set.prototype.add called on incompatible receiver #at Set.add ( ) > Set.prototype Set {}
并且非常困惑為什么一個(gè)方法在它的原型上工作,但是另一個(gè)方法在它的原型上卻不工作。 不幸的是,這種問題你 Google 不到, Stack Overflow 可能也解決不了你的疑惑。
這個(gè)時(shí)候你就應(yīng)該去閱讀 Ecmascript 語言規(guī)范。
或者,您可能想知道臭名昭著的 loose equality operator (==) 是如何工作的(這里松散地使用了單詞 `"function"[ WAT ])。 作為一個(gè)勤奮好學(xué)的程序員,你在 MDN 上查找它 paragraphs of explanation ,卻發(fā)現(xiàn)上面的解釋讓你一臉懵逼。
這個(gè)時(shí)候你就應(yīng)該去閱讀 Ecmascript 語言規(guī)范。
另一方面,我不建議初次接觸 JavaScript 的開發(fā)人員閱讀 ECMAScript 規(guī)范。 如果你是 JavaScript 的新手,那么就開開心心的玩玩 web 吧! 開發(fā)一些 web 應(yīng)用,或者一些基于 Javascript 的攝像頭等。當(dāng)你踩了足夠多的 JavaScript 坑,或者 JavaScript 問題已經(jīng)無法再限制你的開發(fā)能力的時(shí)候,你就可以考慮回到這個(gè)文檔了。
好了,你現(xiàn)在已經(jīng)知道,JavaScript 規(guī)范在幫助您理解語言或平臺(tái)的復(fù)雜性方面非常有用。 但是 ECMAScript 規(guī)范究竟包含哪些東西呢?
什么屬于 ECMAScript 規(guī)范,什么不屬于教科書對這個(gè)問題的回答是 "ECMAScript 規(guī)范中只包含語言特性" 但這并沒有幫助,因?yàn)檫@就像是在說 "JavaScript 特性就是 JavaScript" 我不喜歡重言式[ XKCD-703]。
相反,我要做的是列出一些在 JavaScript 應(yīng)用程序中常見的東西,并告訴你它們是否是一個(gè)語言特性。
特性 | 是否屬于 |
---|---|
Syntax of syntactic elements (i.e., what a valid for..in loop looks like) | Y |
Semantics of syntactic elements (i.e., what typeof null, or { a: b } returns) | Y |
import a from "a"; | ? [1] |
Object, Array, Function, Number, Math, RegExp, Proxy, Map, Promise, ArrayBuffer, Uint8Array, ... | Y |
console, setTimeout(), setInterval(), clearTimeout(), clearInterval() | N [2] |
Buffer, process, global* | N [3] |
module, exports, require(), __dirname, __filename | N [4] |
window, alert(), confirm(), the DOM (document, HTMLElement, addEventListener(), Worker, ...) | N [5] |
規(guī)范指定了這些聲明的語法以及它們的意思,但沒有指定如何加載模塊。
這些東西可以在瀏覽器和 Node.js 中使用,但不是標(biāo)準(zhǔn)的。 對于 Node.js,它們有對應(yīng)的 Nodejs 文檔。 對于瀏覽器,console由 Console 標(biāo)準(zhǔn) 定義,而其余部分由 HTML 標(biāo)準(zhǔn) 指定。
這些都是 Node.js 僅支持的全局變量,由 Nodejs 文檔 定義。 * global 實(shí)際上有機(jī)會(huì)成為 ECMAScript 的一部分,并在瀏覽器中實(shí)現(xiàn) ECMA-262-GLOBAL 。
這些是僅包含 node.js 模塊的"globals",由 其文檔 記錄 / 指定。
這些都是瀏覽器專用的東西。
在哪里查看 ECMAScript 規(guī)范?當(dāng)你在 Google "ECMAScript specification"時(shí),你會(huì)看到很多結(jié)果,都聲稱是合法的規(guī)范。 你應(yīng)該讀哪一本?
更有可能的是,在 tc39.github.io/ecma262/ 上發(fā)布的規(guī)范正是您想要的 ECMA-262。
長話短說:
Ecmascript 語言規(guī)范是由一群來自不同背景的人開發(fā)的,他們被稱為 ECMA 國際技術(shù)委員會(huì)39(或者他們更熟悉的名字 TC39[ TC39])。 TC39 在 TC39.github.io [ ECMA-262]維護(hù) ECMAScript 語言的最新規(guī)范。
讓事情變得復(fù)雜的是,每年 TC39都會(huì)選擇一個(gè)時(shí)間點(diǎn)對規(guī)范進(jìn)行快照,以便成為當(dāng)年的 ECMAScript 語言標(biāo)準(zhǔn),并附帶一個(gè)版本號(hào)。 例如,ECMAScript 2018語言規(guī)范(ECMA-262,第9版) ECMA-262-2018僅僅是2018年6月在 tc39.github.io[ ECMA-262]中看到的規(guī)范,放入歸檔庫中,并進(jìn)行適當(dāng)?shù)匕?PDFified 以供永久存檔。
正因?yàn)槿绱?,除非你希望你?web 應(yīng)用程序從 2018 年 6 月開始只能運(yùn)行在放入 formaldehyde、適當(dāng)包裝和 PDFified 以便永久存檔的瀏覽器上,否則你應(yīng)該始終查看 tc39.github.io [ECMA-262]的最新規(guī)范。 但是,如果您希望(或者必須)支持舊版本的瀏覽器或 Node.js 版本,那么引用舊版本的規(guī)范可能會(huì)有所幫助。
注: iso/iec 也將 ECMAScript 語言標(biāo)準(zhǔn)發(fā)表為 iso/iec16262[ ISO-16262-2011]。 不過不用擔(dān)心,因?yàn)檫@個(gè)標(biāo)準(zhǔn)的文本和 ECMA 國際出版的標(biāo)準(zhǔn)文本完全一樣,唯一的區(qū)別是你必須支付 198 瑞士法郎。Navigating the spec 規(guī)范導(dǎo)航
Ecmascript 規(guī)范談?wù)摿舜罅康膬?nèi)容。 盡管它的作者盡最大努力把它分割成小的邏輯塊,它仍然是一個(gè)超級(jí)龐大的文本。
就我個(gè)人而言,我喜歡把規(guī)格分為五個(gè)部分:
Conventions and basics 約定和基礎(chǔ) (什么是數(shù)字? 當(dāng)規(guī)范說 throw a TypeError exception 是什么意思?)
Grammar productions of the language 語言的語法結(jié)果 (如何編寫 for-in 循環(huán)?)
Static semantics of the language 語言的靜態(tài)語義 (var 語句中如何確定變量名稱?)
Runtime semantics of the language 語言的運(yùn)行時(shí)語義 (for-in 循環(huán)是如何執(zhí)行的?)
APIs (String.prototype.substring() 做什么?)
但規(guī)范不是這樣組織的。 相反,它把第一個(gè)要點(diǎn)放在 §5 Notational Conventions 通過 §9 Ordinary and Exotic Objects Behaviours,接下來的三個(gè)以交叉的形式放在 §10 ECMAScript Language: Source Code 通過 §15 ECMAScript Language: Scripts and Modules,像:
§13.6 The if Statement Grammar productions
§13.6.1-6 Static semantics
§13.6.7 Runtime sematics
§13.7 Iteration Statements Grammar productions
- §13.7.1 Shared static and runtime semantics - §13.7.2 The `do-while` Statement - §13.7.2.1-5 Static semantics - §13.7.2.6 Runtime semantics§13.7.3 The while Statement
...
而 APIs 則通過 §18 The Global Object 通過 §26 Reflection 擴(kuò)展全局對象。
在這一點(diǎn)上,我想指出的是,絕對沒有人從上到下閱讀規(guī)范。 相反,只看與你要查找的內(nèi)容相對應(yīng)的部分,在該部分中只看您需要的內(nèi)容。 試著確定你的具體問題涉及的五大部分中的哪一部分; 如果你無法確定哪一部分是相關(guān)的,問問自己這樣一個(gè)問題:"在什么時(shí)候(無論你想要確認(rèn)什么)這個(gè)問題被評(píng)估了?" 這可能會(huì)有幫助。 不要擔(dān)心,操作規(guī)范只會(huì)在實(shí)踐中變得更容易。
Runtime semantics 運(yùn)行時(shí)語義Runtime semantics of The Language 和 APIs 的運(yùn)行時(shí)語義是規(guī)范中最重要的部分,通常是人們最關(guān)心的問題。
總的來說,在規(guī)范中閱讀這些章節(jié)是非常簡單的。 然而,該規(guī)范使用了大量的 shorthands,這些 shorthands 剛剛開始(至少對我來說)是相當(dāng)討厭的。 我將嘗試解釋其中的一些約定,然后將它們應(yīng)用到一個(gè)通常的工作流程中,以弄清楚幾件事情是如何工作的。
Algorithm steps 算法步驟Ecmascript 中的大多數(shù) runtime semantics (運(yùn)行時(shí)語義) 是由一系列 algorithm steps (算法步驟) 指定的,與偽代碼沒有什么不同,但形式精確得多。
A sample set of algorithm steps are:
Let a be 1.
Let b be a+a.
If b is 2, then
Hooray! Arithmetics isn’t broken.
Else
Boo!
拓展閱讀:§5.2 Algorithm ConventionsAbstract operations 抽象操作
你有時(shí)會(huì)看到類似函數(shù)的東西在 spec 中被調(diào)用。 Boolean() 函數(shù)的第一步是:
當(dāng)使用參數(shù)值調(diào)用 Boolean 時(shí),將執(zhí)行以下步驟:
Let b be ToBoolean(value).
...
這個(gè) ToBoolean 函數(shù)被稱為 abstract operation (抽象操作):它是抽象的,因?yàn)樗鼘?shí)際上并沒有作為一個(gè)函數(shù)暴露給 JavaScript 代碼。 它只是一個(gè) notation spec writers (符號(hào)規(guī)范作者)發(fā)明的,讓他們不要寫同樣的東西一遍又一遍。
拓展閱讀:§5.2.1 Abstract OperationsWhat is [[This]]
有時(shí)候,你可能會(huì)看到 [[Notation]] 被用作 "Let proto be obj.[[Prototype]]"。 這個(gè)符號(hào)在技術(shù)上可能意味著幾個(gè)不同的東西,這取決于它出現(xiàn)的上下文,但是你可以理解,這個(gè)符號(hào)指的是一些不能通過 JavaScript 代碼觀察到的內(nèi)部屬性。
準(zhǔn)確地說,它可以意味著三種不同的東西,我將用規(guī)范中的例子來說明它們。 不過,你可以暫時(shí)跳過它們。
A field of a RecordEcmascript 規(guī)范使用術(shù)語 Record 來引用具有固定鍵集的 key-value map ——有點(diǎn)像 C 語言中的結(jié)構(gòu)。 Record 的每個(gè) key-value 對稱為 field。 因?yàn)?Records 只能出現(xiàn)在規(guī)范中,而不能出現(xiàn)在實(shí)際的 JavaScript 代碼中,所以使用 [[Notation]]來引用 Record 的 field 是有意義的。
Notably, Property Descriptors are also modeled as Records with fields [[Value]], [[Writable]], [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]. The IsDataDescriptor abstract operation uses this notation extensively:
When the abstract operation IsDataDescriptor is called with Property Descriptor Desc, the following steps are taken:
If Desc is undefined, return false.
If both Desc.[[Value]] and Desc.[[Writable]] are absent, return false.
Return true.
另一個(gè) Records 的具體例子可以在下一節(jié)中找到, §2.4 Completion Records; ? and !
拓展閱讀: §6.2.1 The List and Record Specification TypesAn internal slot of a JavaScript Object
Javascript 對象可能有所謂的 internal slots ,規(guī)范使用這些 internal slots 來保存數(shù)據(jù)。 像 Record 字段一樣,這些 internal slots 也不能用 JavaScript 觀察到,但是其中一些可能會(huì)通過特定于實(shí)現(xiàn)的工具(如 Google Chrome 的 DevTools)暴露出來。 因此,使用 [[Notation]] 來描述 internal slots 也是有意義的。
internal slots 的細(xì)節(jié)將在 §2.5 JavaScript Objects 中介紹。 現(xiàn)在,不要過于擔(dān)心它們的用途,但請注意下面的例子。
An internal method of a JavaScript ObjectMost JavaScript Objects have an internal slot [[Prototype]] that refers to the Object they inherit from. The value of this internal slot is usually the value that Object.getPrototypeOf() returns. In the OrdinaryGetPrototypeOf abstract operation, the value of this internal slot is accessed:
When the abstract operation OrdinaryGetPrototypeOf is called with Object O, the following steps are taken:
Return O.[[Prototype]].
注意: Object 和 Record fields 的 Internal slots 在外觀上是相同的,但是可以通過查看這個(gè)符號(hào)(點(diǎn)之前的部分)的先例來消除它們的歧義,無論它是 Object 還是 Record。 從周圍的環(huán)境來看,這通常是相當(dāng)明顯的。
Javascript 對象也可能有所謂的 internal methods。 像 internal slots 一樣,這些 internal methods 不能通過 JavaScript 直接觀察到。 因此,使用 [[Notation]] 來描述 internal methods 也是有意義的。
internal methods 的細(xì)節(jié)將在 §2.5 JavaScript Objects 中介紹。 現(xiàn)在,不要過于擔(dān)心它們的用途,但請注意下面的例子。
Completion Records; ? and !All JavaScript functions have an internal method [[Call]] that runs that function. The Call abstract operation has the following step:
Return ? F.[[Call]](V, argumentsList).
where F is a JavaScript function object. In this case, the [[Call]] internal method of F is itself called with arguments V and argumentsList.
注意: [[[Notation]]的第三種意義可以通過看起來像一個(gè)函數(shù)調(diào)用來區(qū)分。
Ecmascript 規(guī)范中的每個(gè)運(yùn)行時(shí)語義都顯式或隱式地返回一個(gè)報(bào)告其結(jié)果的 Completion Record。 這個(gè) Completion Record 是一個(gè)Record,有三個(gè)可能的領(lǐng)域:
一個(gè) [[Type]] (normal, return, throw, break, 或 continue)
如果 [[Type]] 是正常的, return, or throw, 那么它也可以有 [[Value]] ("what’s returned/thrown")
如果[[Type]] 是中斷或繼續(xù), 那么它可以選擇帶有一個(gè)稱為 [[Target]] 的標(biāo)簽,由于運(yùn)行時(shí)語義的原因,腳本執(zhí)行 breaks from/continues
A Completion Record whose [[Type]] is normal is called a normal completion. Every Completion Record other than a normal completion is also known as an abrupt completion.
大多數(shù)情況下,您只需要處理 abrupt completions 的 [[ Type ]] 是 throw。 其他三種 abrupt completion 類型僅在查看如何評(píng)估特定語法元素時(shí)有用。 實(shí)際上,在內(nèi)置函數(shù)的定義中,您永遠(yuǎn)不會(huì)看到任何其他類型,因?yàn)?break / continue / return 不跨函數(shù)邊界工作。
拓展閱讀:§6.2.3 The Completion Record Specification Type
由于 Completion Records 的定義,JavaScript 中的細(xì)節(jié)(如冒泡錯(cuò)誤,直到 try-catch 塊)在規(guī)范中不存在。 事實(shí)上,錯(cuò)誤(或更準(zhǔn)確地說是 abrupt completions)是顯式處理的。
沒有任何縮寫,對抽象操作的普通調(diào)用的規(guī)范文本可能會(huì)返回一個(gè)計(jì)算結(jié)果或拋出一個(gè)錯(cuò)誤,它看起來像:
下面是一些調(diào)用抽象操作的步驟,這些步驟可以拋出 without any shorthands 的操作:
Let resultCompletionRecord be AbstractOp().
Note: resultCompletionRecord is a Completion Record.
If resultCompletionRecord is an abrupt completion, return resultCompletionRecord.
注意: 在這里,如果是 abrupt completion,則直接返回 resultCompletionRecord。 換句話說,會(huì)轉(zhuǎn)發(fā) AbstractOp 中拋出的錯(cuò)誤,并終止其余的步驟。
Let result be resultCompletionRecord.[[Value]].
注意: 在確保得到 normal completion 后,我們現(xiàn)在可以解構(gòu) Completion Record 以得到我們需要的計(jì)算的實(shí)際結(jié)果。
result 就是我們需要的結(jié)果。 我們現(xiàn)在可以用它做更多的事情。
這可能會(huì)模糊地讓你想起 C 語言中的手動(dòng)錯(cuò)誤處理:
int result = abstractOp(); // Step 1 if (result < 0) // Step 2 return result; // Step 2 (continued) // Step 3 is unneeded // func() succeeded; carrying on... // Step 4
但是為了減少這些繁瑣的步驟,ECMAScript 規(guī)范的編輯器增加了一些縮寫。 自 ES2016 以來,同樣的規(guī)范文本可以用以下兩種等價(jià)的方式編寫:
下面的幾個(gè)步驟可以調(diào)用一個(gè)抽象操作,這個(gè)操作可能會(huì)拋出 ReturnIfAbrupt:
Let result be AbstractOp().
注意: 這里,就像前面例子中的步驟1一樣,結(jié)果是一個(gè) Completion Record.
ReturnIfAbrupt(result).
Note: 注意: ReturnIfAbrupt 通過轉(zhuǎn)發(fā)來處理任何可能出現(xiàn)的 abrupt completions,并自動(dòng)將 result 解構(gòu)到它的 [[Value]]
result 就是我們需要的結(jié)果。 我們現(xiàn)在可以用它做更多的事情。
或者,更準(zhǔn)確地說,用一個(gè)特殊的問號(hào) (?) 符號(hào):
調(diào)用可能帶有問號(hào)(?)的抽象操作的幾個(gè)步驟 :
Let result be ? AbstractOp().
注意:在這個(gè) notation 中,我們根本不處理 Completion Records 。 ? shorthand 為我們處理了一切事情, 且 result 立馬就可用
result 就是我們需要的結(jié)果。 我們現(xiàn)在可以用它做更多的事情。
有時(shí),如果我們知道對 AbstractOp 的特定調(diào)用永遠(yuǎn)不會(huì)返回一個(gè) abrupt completion,那么它可以向讀者傳達(dá)更多關(guān)于 spec’s intent。 在這些情況下,一個(gè) exclamation mark (!) 用于:
A few steps that call an abstract operation that cannot ever throw with an exclamation mark (!):Let result be ! AbstractOp().
Note: While ? forwards any errors we may have gotten, ! asserts that we never get any abrupt completions from this call, and it would be a bug in the specification if we did. Like the case with ?, we don’t deal with Completion Records at all. result is ready to use immediately after.
result is the result we need. We can now do more things with it.
拓展閱讀: §5.2.3.4 ReturnIfAbrupt ShorthandsJavaScript Objects
在 ECMAScript 中,每個(gè) Object 都有一組內(nèi)部方法,規(guī)范的其余部分調(diào)用這些方法來完成某些任務(wù)。 所有 object 都有的一些內(nèi)部方法是:
[[Get]], which gets a property on an Object (e.g. obj.prop)
[[Set]], which sets a property on an Object (e.g. obj.prop = 42;)
[[GetPrototypeOf]], which gets the Object’s prototype (i.e., Object.getPrototypeOf(obj))
[[GetOwnProperty]], which gets the Property Descriptor of an own property of an Object (i.e., Object.getOwnPropertyDescriptor(obj, "prop"))
[[Delete]], which deletes a property on an Object (e.g. delete obj.prop)
詳盡的列表可在 §6.1.7.2 Object Internal Methods and Internal Slots 中找到。
基于這個(gè)定義,function objects (or just "functions") 是簡單的對象,它們附加了 [[Call]] 內(nèi)部方法,可能還有[[ Construct ]]內(nèi)部方法; 因此,它們也被稱為 callable objects。
然后,規(guī)范將所有 Object 分為兩類: ordinary objects 和 exotic objects。 你遇到的大多數(shù)對象都是 ordinary objects,這意味著它們所有的內(nèi)部方法都是在 §9.1 Ordinary Object Internal Methods and Internal Slots。
然而,ECMAScript 規(guī)范還定義了一些 exotic objects,這些對象可以覆蓋這些內(nèi)部方法的默認(rèn)實(shí)現(xiàn)。 對于允許外來對象執(zhí)行的操作,有一定的最小限制,但是一般來說,過多的內(nèi)部方法可以執(zhí)行大量的特技操作,而不違反規(guī)范。
Array 對象是這些 exotic objects 的一種。 使用 ordinary objects 可用的工具無法獲取像 Array 對象的 length 屬性的一些特殊語義。其中之一是,設(shè)置 Array 對象的 length 屬性可以從對象中刪除屬性,但 length 屬性似乎只是一個(gè)普通的數(shù)據(jù)屬性。 相比之下,new Map().size 只是 Map.prototype 上指定的 getter 函數(shù),不具有類似于 [].length 的屬性。
> const arr = [0, 1, 2, 3]; > console.log(arr); [ 0, 1, 2, 3 ] > arr.length = 1; > console.log(arr); [ 0 ] > console.log(Object.getOwnPropertyDescriptor([], "length")); { value: 1, writable: true, enumerable: false, configurable: false }
> console.log(Object.getOwnPropertyDescriptor(new Map(), "size")); undefined > console.log(Object.getOwnPropertyDescriptor(Map.prototype, "size")); { get: [Function: get size], set: undefined, enumerable: false, configurable: true }
這種行為是通過重寫 [[DefineOwnProperty]] 內(nèi)部方法來實(shí)現(xiàn)的。 詳見 §9.4.2 Array Exotic Objects
Javascript 對象也可能有定義為包含特定類型值的 internal slots 。 我傾向于將 internal slots 視為甚至對 Object.getOwnPropertySymbols() 都隱藏的以符號(hào)命名的屬性。ordinary objects 和 exotic objects 都允許有 internal slots。
在 An internal slot of a JavaScript Object 中, 我提到了大多數(shù)對象都具有的一個(gè)名為 [[Prototype]] 的 internal slot 。 (事實(shí)上,所有的 ordinary objects ,甚至一些 exotic objects ,比如 Array 對象都有它。) 但是我們也知道有一個(gè)叫[[GetPrototypeOf]] 的內(nèi)部方法,我在上面簡要描述過。 它們之間有什么區(qū)別嗎?
這里的關(guān)鍵字是 most: 雖然大多數(shù)對象都有 [[Prototype]] internal slot,但所有對象都實(shí)現(xiàn) [[GetPrototypeOf]] 內(nèi)部方法。 值得注意的是,Proxy 對象沒有它們自己的 [[Prototype]] ,而且它的 [[GetPrototypeOf]] 內(nèi)部方法遵從已注冊的處理程序或其目標(biāo)的原型,存儲(chǔ)在 Proxy 對象的 [[ProxyTarget]] 的 internal slot 中。
因此,在處理對象時(shí),引用適當(dāng)?shù)?internal method 幾乎總是一個(gè)好主意,而不是直接查看 internal slot 的值。
另一種思考 Objects, internal methods 和 internal slots 之間關(guān)系的方式是通過經(jīng)典的 object-oriented lens。 "Object"類似于指定必須實(shí)現(xiàn)的幾個(gè) internal methods 的接口。 ordinary objects 提供了缺省實(shí)現(xiàn),exotic objects 可以部分或全部覆蓋。 另一方面,internal slots 似于 Object 的實(shí)例變量 —— Object 的實(shí)現(xiàn)細(xì)節(jié)。
所有這些關(guān)系都可以用下面的 UML 圖來概括:
示例: String.prototype.substring()現(xiàn)在我們已經(jīng)很好地理解了規(guī)范是如何組織和編寫的,讓我們開始練習(xí)吧!
假設(shè)我現(xiàn)在有以下問題:
如果不運(yùn)行代碼,下面的代碼片段返回什么?
String.prototype.substring.call(undefined, 2, 4)
這是一個(gè)相當(dāng)棘手的問題。 似乎有兩種看似合理的結(jié)果:
String.prototype.substring() 可以首先將 undefined 強(qiáng)制轉(zhuǎn)換為 "undefined" 字符串,然后在該字符串的第二和第三個(gè)位置(即間隔 [2,4] )獲取字符得到 "de"。
另一方面,String.prototype.substring() 也可以合理地拋出一個(gè)錯(cuò)誤,從而拒絕未定義的輸入。
不幸的是,當(dāng) this 的值不是字符串時(shí),MDN 也沒有真正提供任何關(guān)于函數(shù)運(yùn)行的說明。
在閱讀 algorithm steps 之前,讓我們先想想我們知道什么。 我假設(shè)我們已經(jīng)對 str.substring() 的通常工作方式有了基本的了解:即返回給定字符串的一部分。 我們現(xiàn)在真正不確定的是,在 this 值為 undefined 的情況下,它是如何運(yùn)作的。 因此,我們將特別尋找解決 this 值的 algorithm steps。
幸運(yùn)的是,String.prototype.substring() algorithm 的第一步專門處理這個(gè)值:
Let O be ? RequireObjectCoercible(this value).
? shorthand 允許我們得出這樣的結(jié)論: 在某些情況下,RequireObjectCoercible 抽象操作實(shí)際上可能會(huì)拋出異常,因?yàn)榉駝t ! 會(huì)被用來代替。 事實(shí)上,如果它拋出一個(gè)錯(cuò)誤,它將與我們上面的第二個(gè)假設(shè)相對應(yīng)! 有了希望,我們可以通過單擊超鏈接來了解 RequireObjectCoercible 做了什么。
Requireobjectforecble 抽象操作有點(diǎn)奇怪。 與大多數(shù)抽象操作不同,它是通過表格而不是步驟來定義的:
Argument Type | Result |
---|---|
Undefined 的 | Throw a TypeError exception |
... | ... |
不管怎樣——在對應(yīng)于 Undefined (我們傳遞給 substring() 的 this 值的類型)的行中,規(guī)范說 RequireObjectCoercible 應(yīng)該拋出一個(gè)異常。 那是因?yàn)樵?函數(shù)的定義中使用 ? ,我們知道拋出的異常必須冒泡到函數(shù)的調(diào)用方。
這就是我們的答案: 給定的代碼片段會(huì)拋出一個(gè) TypeError 異常。
規(guī)范只指定了錯(cuò)誤拋出的類型,而沒有指定它包含的消息。 這意味著實(shí)現(xiàn)可以有不同的錯(cuò)誤消息,甚至是本地化的錯(cuò)誤消息。示例: Boolean() 和 String() 會(huì)拋出異常嗎?例如,在谷歌的 v86.4(包含在谷歌 Chrome 64中)上,消息是:
TypeError: String.prototype.substring called on null or undefined
而 Mozilla Firefox 57.0提供了一些不那么有用的功能
TypeError: can’t convert undefined to object
與此同時(shí),ChakraCore version 1.7.5.0(Microsoft Edge 中的 JavaScript 引擎)采用了 V8的路線并拋出
TypeError: String.prototype.substring: "this" is null or undefined
在編寫關(guān)鍵任務(wù)代碼時(shí),必須優(yōu)先考慮異常處理。 因此,"某個(gè)內(nèi)置函數(shù)會(huì)拋出異常嗎?" 需要仔細(xì)琢磨。
在本例中,我們將嘗試回答兩個(gè)語言內(nèi)置函數(shù) Boolean() 和 String() 的問題。 我們將只關(guān)注對這些函數(shù)的直接調(diào)用,而不是 new Boolean() 和 new String() ——這是 JavaScript 中最不受歡迎的特性 之一,也是幾乎所有 JS 編程指南中最不鼓勵(lì)的實(shí)踐 YDKJS。
在找到規(guī)范中的 Boolean() 部分之后,我們看到算法似乎相當(dāng)簡短:
當(dāng)使用參數(shù)值調(diào)用 Boolean 時(shí),將執(zhí)行以下步驟:
Let b be ToBoolean(value).
If NewTarget is undefined, return b.
Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%BooleanPrototype%", ? [[BooleanData]] ?).
Set O.[[BooleanData]] to b.
Return O.
但是從另一方面來說,這并不是完全簡單的,在 OrdinaryCreateFromConstructor 這里涉及到一些復(fù)雜的基本技巧。 更重要的是,有一個(gè) ? 步驟 3 中的簡寫,可能表明此函數(shù)在某些情況下可能拋出錯(cuò)誤。 讓我們仔細(xì)看看。
步驟1將 value (函數(shù)參數(shù))轉(zhuǎn)換為布爾值。 有趣的是,沒有一個(gè) ? 或者 !這一步驟的 shorthand ,但通常沒有 Completion Record shorthand 等同于 ! . 因此,步驟 1 不能拋出異常。
第 2 步檢查名為 NewTarget 的東西是否 undefined。 Newtarget 是在 ES2015 中首次添加的 new.target 元屬性的 spec 等價(jià)物,它允許規(guī)范區(qū)分 new Boolean() 調(diào)用。 因?yàn)槲覀儸F(xiàn)在只關(guān)注對 Boolean() 的直接調(diào)用,所以我們知道 NewTarget 總是未定義的,并且算法總是直接返回 b,而不需要任何額外的處理。
因?yàn)檎{(diào)用不帶 new 的 Boolean() 只能訪問 Boolean() 算法中的前兩個(gè)步驟,而這兩個(gè)步驟都不能引發(fā)異常,所以我們得出結(jié)論: 無論輸入是什么,Boolean() 都不會(huì)引發(fā)異常。
讓我們把注意力轉(zhuǎn)向 String () :
當(dāng)使用參數(shù)值調(diào)用 String 時(shí),將執(zhí)行以下步驟:
If no arguments were passed to this function invocation, let s be "".
Else,
If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value).
Let s be ? ToString(value).
If NewTarget is undefined, return s.
Return ? StringCreate(s, ? GetPrototypeFromConstructor(NewTarget, "%StringPrototype%")).
根據(jù)我們使用 Boolean() 函數(shù)進(jìn)行同類分析的經(jīng)驗(yàn),我們知道對于我們的案例,NewTarget 總是未定義的,因此可以跳過最后一步。 我們還知道 Type 和 SymbolDescriptiveString 也是安全的,因?yàn)樗鼈兌疾粫?huì)處理 abrupt completions。 然而,還有一個(gè)關(guān)于 ? 的問題嗎? 在調(diào)用 ToString 抽象操作之前。 讓我們仔細(xì)看看。
就像我們前面看到的 RequireObjectCoercible 一樣,ToString(argument) 也是用一個(gè)表定義的:
Argument Type | Result |
---|---|
Undefined | Return "undefined" |
Null | Return "null" |
Boolean | If argument is true, return "true" If argument is false, return "false" |
Number | Return NumberToString(argument) |
String | Return argument |
Symbol | Throw a TypeError exception |
Object | Apply the following steps: 1. Let primValue be ? ToPrimitive(argument, hint String) 2. Return ? ToString(primValue) |
在 String() 中調(diào)用 ToString 時(shí),value 可以是 Symbol 以外的任何值(在緊接著的步驟中過濾掉)。 然而,還有兩個(gè) ? 對象行中的。 我們可以點(diǎn)擊 ToPrimitive 和 beyond 的鏈接,發(fā)現(xiàn)如果 value 是 Object,那么實(shí)際上有很多機(jī)會(huì)拋出錯(cuò)誤:
所以對于 String() ,我們的結(jié)論是它從不為 primitive values 拋出異常,但可能為 Objects 拋出錯(cuò)誤。
更多關(guān)于 String() throws 的例子如下:
// Spec stack trace: // OrdinaryGet step 8. // Ordinary Object’s [[Get]]() step 1. // GetV step 3. // GetMethod step 2. // ToPrimitive step 2.d. String({ get [Symbol.toPrimitive]() { throw new Error("Breaking JavaScript"); } });
// Spec stack trace: // GetMethod step 4. // ToPrimitive step 2.d. String({ get [Symbol.toPrimitive]() { return "Breaking JavaScript"; } });
// Spec stack trace: // ToPrimitive step 2.e.i. String({ [Symbol.toPrimitive]() { throw new Error("Breaking JavaScript"); } });
// Spec stack trace: // ToPrimitive step 2.e.iii. String({ [Symbol.toPrimitive]() { return { "breaking": "JavaScript" }; } });
// Spec stack trace: // OrdinaryToPrimitive step 5.b.i. // ToPrimitive step 2.g. String({ toString() { throw new Error("Breaking JavaScript"); } });
// Spec stack trace: // OrdinaryToPrimitive step 5.b.i. // ToPrimitive step 2.g. String({ valueOf() { throw new Error("Breaking JavaScript"); } });
// Spec stack trace: // OrdinaryToPrimitive step 6. // ToPrimitive step 2.g. String(Object.create(null));示例:typeof operator
到目前為止,我們只分析了 API 函數(shù),讓我們嘗試一些不同的東西。
未完待續(xù) https://github.com/TimothyGu/...參考
How to Read the ECMAScript Specification
ECMAScript? 2020 Language Specification
JavaScript深入之從ECMAScript規(guī)范解讀this
讀懂 ECMAScript 規(guī)格
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/109679.html
摘要:完整清單是中添加,此處不予介紹布爾值用來表示可能是真或假的值。結(jié)果抽象比較運(yùn)算符在比較它們之前在類型之間進(jìn)行自動(dòng)轉(zhuǎn)換。中的隱式轉(zhuǎn)換稱為強(qiáng)制類型轉(zhuǎn)換,并在規(guī)范中定義。這些內(nèi)置類型可用于在不同類型之間進(jìn)行顯式轉(zhuǎn)換。 翻譯:瘋狂的技術(shù)宅原文:https://www.valentinog.com/bl... 本文首發(fā)微信公眾號(hào):前端先鋒歡迎關(guān)注,每天都給你推送新鮮的前端技術(shù)文章 show...
摘要:詞法分析對構(gòu)成源程序的字符流進(jìn)行掃描然后根據(jù)構(gòu)詞規(guī)則識(shí)別單詞也稱單詞符號(hào)或符號(hào)。語義分析是編譯過程的一個(gè)邏輯階段語義分析的任務(wù)是對結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查進(jìn)行類型審查,審查抽象語法樹是否符合該編程語言的規(guī)則。 1. 文章的內(nèi)容和主題 我對編譯器的深入了解起源于一條推特中的問題:Angular是如何用Angular預(yù)先編譯器(AOT)對靜態(tài)代碼進(jìn)行解析工作的。在進(jìn)行一些...
摘要:提交內(nèi)容可以是一個(gè)提議想法初步描述該階段是對所提交新特性的正式建議。在這個(gè)階段需具備以下條件指定一名成員作為審閱通過有實(shí)現(xiàn)的或者初步編寫標(biāo)準(zhǔn),包括問題描述解決方案示例語法語義關(guān)鍵的算法及抽象實(shí)現(xiàn)在的復(fù)雜度等該階段是會(huì)出現(xiàn)標(biāo)準(zhǔn)中的第一個(gè)版本。 ECMAScript 與 JavaScript ECMAScript 是一套腳本語言的規(guī)范,內(nèi)部編號(hào) ECMA-262 該規(guī)范由 Ecma(Eu...
摘要:提交內(nèi)容可以是一個(gè)提議想法初步描述該階段是對所提交新特性的正式建議。在這個(gè)階段需具備以下條件指定一名成員作為審閱通過有實(shí)現(xiàn)的或者初步編寫標(biāo)準(zhǔn),包括問題描述解決方案示例語法語義關(guān)鍵的算法及抽象實(shí)現(xiàn)在的復(fù)雜度等該階段是會(huì)出現(xiàn)標(biāo)準(zhǔn)中的第一個(gè)版本。 ECMAScript 與 JavaScript ECMAScript 是一套腳本語言的規(guī)范,內(nèi)部編號(hào) ECMA-262 該規(guī)范由 Ecma(Eu...
閱讀 2859·2021-09-28 09:36
閱讀 3975·2021-09-22 15:52
閱讀 3645·2021-09-06 15:00
閱讀 1966·2021-09-02 15:40
閱讀 2812·2021-09-02 15:15
閱讀 3478·2021-08-17 10:15
閱讀 2792·2019-08-30 15:53
閱讀 2082·2019-08-29 18:39