摘要:另一方面,我不建議初次接觸的開發(fā)人員閱讀規(guī)范。在維護語言的最新規(guī)范。在這一點上,我想指出的是,絕對沒有人從上到下閱讀規(guī)范。拓展閱讀由于的定義,中的細節(jié)如冒泡錯誤,直到塊在規(guī)范中不存在。換句話說,會轉發(fā)中拋出的錯誤,并終止其余的步驟。
翻譯自:How to Read the ECMAScript Specification
Ecmascript 語言規(guī)范 The ECMAScript Language specification(又名:Javascript 規(guī)范 the JavaScript specification 或 ECMA-262)是學習 JavaScript 底層工作原理的非常好的資源。 然而,這是一個龐大的專業(yè)文本資料,咋一眼看過去,大家可能會感到迷茫、恐懼,滿懷激情卻無從下手。
前言不管你是打算每天閱讀一點 ECMAScript 規(guī)范,還是把它當成一個年度或者季度的目標,這篇文章旨在讓你更輕松的開始閱讀最權威的 JavaScript 語言參考資料。
為什么要閱讀 ECMAScript 規(guī)范Ecmascript 規(guī)范是所有 JavaScript 運行行為的權威來源,無論是在你的瀏覽器環(huán)境,還是在服務器環(huán)境( Node.js ),還是在宇航服上[ NodeJS-NASA ] ,或在你的物聯(lián)網(wǎng)設備上[ JOHNNY-FIVE ]。 所有 JavaScript 引擎的開發(fā)者都依賴于這個規(guī)范來確保他們各種天花亂墜的新特性能夠其他 JavaScript 引擎一樣,按預期工作。
Ecmascript 規(guī)范 絕不僅僅對 JavaScript 引擎開發(fā)者有用,它對普通的 JavaScript 編碼人員也非常有用,而你只是沒有意識到或者沒有用到。
假設有一天你在工作中發(fā)現(xiàn)了下面這個奇怪的問題:
> 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 {}
并且非常困惑為什么一個方法在它的原型上工作,但是另一個方法在它的原型上卻不工作。 不幸的是,這種問題你 Google 不到, Stack Overflow 可能也解決不了你的疑惑。
這個時候你就應該去閱讀 Ecmascript 語言規(guī)范。
或者,您可能想知道臭名昭著的 loose equality operator (==) 是如何工作的(這里松散地使用了單詞 `"function"[ WAT ])。 作為一個勤奮好學的程序員,你在 MDN 上查找它 paragraphs of explanation ,卻發(fā)現(xiàn)上面的解釋讓你一臉懵逼。
這個時候你就應該去閱讀 Ecmascript 語言規(guī)范。
另一方面,我不建議初次接觸 JavaScript 的開發(fā)人員閱讀 ECMAScript 規(guī)范。 如果你是 JavaScript 的新手,那么就開開心心的玩玩 web 吧! 開發(fā)一些 web 應用,或者一些基于 Javascript 的攝像頭等。當你踩了足夠多的 JavaScript 坑,或者 JavaScript 問題已經(jīng)無法再限制你的開發(fā)能力的時候,你就可以考慮回到這個文檔了。
好了,你現(xiàn)在已經(jīng)知道,JavaScript 規(guī)范在幫助您理解語言或平臺的復雜性方面非常有用。 但是 ECMAScript 規(guī)范究竟包含哪些東西呢?
什么屬于 ECMAScript 規(guī)范,什么不屬于教科書對這個問題的回答是 "ECMAScript 規(guī)范中只包含語言特性" 但這并沒有幫助,因為這就像是在說 "JavaScript 特性就是 JavaScript" 我不喜歡重言式[ XKCD-703]。
相反,我要做的是列出一些在 JavaScript 應用程序中常見的東西,并告訴你它們是否是一個語言特性。
特性 | 是否屬于 |
---|---|
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 中使用,但不是標準的。 對于 Node.js,它們有對應的 Nodejs 文檔。 對于瀏覽器,console由 Console 標準 定義,而其余部分由 HTML 標準 指定。
這些都是 Node.js 僅支持的全局變量,由 Nodejs 文檔 定義。 * global 實際上有機會成為 ECMAScript 的一部分,并在瀏覽器中實現(xiàn) ECMA-262-GLOBAL 。
這些是僅包含 node.js 模塊的"globals",由 其文檔 記錄 / 指定。
這些都是瀏覽器專用的東西。
在哪里查看 ECMAScript 規(guī)范?當你在 Google "ECMAScript specification"時,你會看到很多結果,都聲稱是合法的規(guī)范。 你應該讀哪一本?
更有可能的是,在 tc39.github.io/ecma262/ 上發(fā)布的規(guī)范正是您想要的 ECMA-262。
長話短說:
Ecmascript 語言規(guī)范是由一群來自不同背景的人開發(fā)的,他們被稱為 ECMA 國際技術委員會39(或者他們更熟悉的名字 TC39[ TC39])。 TC39 在 TC39.github.io [ ECMA-262]維護 ECMAScript 語言的最新規(guī)范。
讓事情變得復雜的是,每年 TC39都會選擇一個時間點對規(guī)范進行快照,以便成為當年的 ECMAScript 語言標準,并附帶一個版本號。 例如,ECMAScript 2018語言規(guī)范(ECMA-262,第9版) ECMA-262-2018僅僅是2018年6月在 tc39.github.io[ ECMA-262]中看到的規(guī)范,放入歸檔庫中,并進行適當?shù)匕?PDFified 以供永久存檔。
正因為如此,除非你希望你的 web 應用程序從 2018 年 6 月開始只能運行在放入 formaldehyde、適當包裝和 PDFified 以便永久存檔的瀏覽器上,否則你應該始終查看 tc39.github.io [ECMA-262]的最新規(guī)范。 但是,如果您希望(或者必須)支持舊版本的瀏覽器或 Node.js 版本,那么引用舊版本的規(guī)范可能會有所幫助。
注: iso/iec 也將 ECMAScript 語言標準發(fā)表為 iso/iec16262[ ISO-16262-2011]。 不過不用擔心,因為這個標準的文本和 ECMA 國際出版的標準文本完全一樣,唯一的區(qū)別是你必須支付 198 瑞士法郎。Navigating the spec 規(guī)范導航
Ecmascript 規(guī)范談論了大量的內(nèi)容。 盡管它的作者盡最大努力把它分割成小的邏輯塊,它仍然是一個超級龐大的文本。
就我個人而言,我喜歡把規(guī)格分為五個部分:
Conventions and basics 約定和基礎 (什么是數(shù)字? 當規(guī)范說 throw a TypeError exception 是什么意思?)
Grammar productions of the language 語言的語法結果 (如何編寫 for-in 循環(huán)?)
Static semantics of the language 語言的靜態(tài)語義 (var 語句中如何確定變量名稱?)
Runtime semantics of the language 語言的運行時語義 (for-in 循環(huán)是如何執(zhí)行的?)
APIs (String.prototype.substring() 做什么?)
但規(guī)范不是這樣組織的。 相反,它把第一個要點放在 §5 Notational Conventions 通過 §9 Ordinary and Exotic Objects Behaviours,接下來的三個以交叉的形式放在 §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 擴展全局對象。
在這一點上,我想指出的是,絕對沒有人從上到下閱讀規(guī)范。 相反,只看與你要查找的內(nèi)容相對應的部分,在該部分中只看您需要的內(nèi)容。 試著確定你的具體問題涉及的五大部分中的哪一部分; 如果你無法確定哪一部分是相關的,問問自己這樣一個問題:"在什么時候(無論你想要確認什么)這個問題被評估了?" 這可能會有幫助。 不要擔心,操作規(guī)范只會在實踐中變得更容易。
Runtime semantics 運行時語義Runtime semantics of The Language 和 APIs 的運行時語義是規(guī)范中最重要的部分,通常是人們最關心的問題。
總的來說,在規(guī)范中閱讀這些章節(jié)是非常簡單的。 然而,該規(guī)范使用了大量的 shorthands,這些 shorthands 剛剛開始(至少對我來說)是相當討厭的。 我將嘗試解釋其中的一些約定,然后將它們應用到一個通常的工作流程中,以弄清楚幾件事情是如何工作的。
Algorithm steps 算法步驟Ecmascript 中的大多數(shù) runtime semantics (運行時語義) 是由一系列 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ù)的東西在 spec 中被調(diào)用。 Boolean() 函數(shù)的第一步是:
當使用參數(shù)值調(diào)用 Boolean 時,將執(zhí)行以下步驟:
Let b be ToBoolean(value).
...
這個 ToBoolean 函數(shù)被稱為 abstract operation (抽象操作):它是抽象的,因為它實際上并沒有作為一個函數(shù)暴露給 JavaScript 代碼。 它只是一個 notation spec writers (符號規(guī)范作者)發(fā)明的,讓他們不要寫同樣的東西一遍又一遍。
拓展閱讀:§5.2.1 Abstract OperationsWhat is [[This]]
有時候,你可能會看到 [[Notation]] 被用作 "Let proto be obj.[[Prototype]]"。 這個符號在技術上可能意味著幾個不同的東西,這取決于它出現(xiàn)的上下文,但是你可以理解,這個符號指的是一些不能通過 JavaScript 代碼觀察到的內(nèi)部屬性。
準確地說,它可以意味著三種不同的東西,我將用規(guī)范中的例子來說明它們。 不過,你可以暫時跳過它們。
A field of a RecordEcmascript 規(guī)范使用術語 Record 來引用具有固定鍵集的 key-value map ——有點像 C 語言中的結構。 Record 的每個 key-value 對稱為 field。 因為 Records 只能出現(xiàn)在規(guī)范中,而不能出現(xiàn)在實際的 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.
另一個 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 觀察到,但是其中一些可能會通過特定于實現(xiàn)的工具(如 Google Chrome 的 DevTools)暴露出來。 因此,使用 [[Notation]] 來描述 internal slots 也是有意義的。
internal slots 的細節(jié)將在 §2.5 JavaScript Objects 中介紹。 現(xià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 在外觀上是相同的,但是可以通過查看這個符號(點之前的部分)的先例來消除它們的歧義,無論它是 Object 還是 Record。 從周圍的環(huán)境來看,這通常是相當明顯的。
Javascript 對象也可能有所謂的 internal methods。 像 internal slots 一樣,這些 internal methods 不能通過 JavaScript 直接觀察到。 因此,使用 [[Notation]] 來描述 internal methods 也是有意義的。
internal methods 的細節(jié)將在 §2.5 JavaScript Objects 中介紹。 現(xià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]]的第三種意義可以通過看起來像一個函數(shù)調(diào)用來區(qū)分。
Ecmascript 規(guī)范中的每個運行時語義都顯式或隱式地返回一個報告其結果的 Completion Record。 這個 Completion Record 是一個Record,有三個可能的領域:
一個 [[Type]] (normal, return, throw, break, 或 continue)
如果 [[Type]] 是正常的, return, or throw, 那么它也可以有 [[Value]] ("what’s returned/thrown")
如果[[Type]] 是中斷或繼續(xù), 那么它可以選擇帶有一個稱為 [[Target]] 的標簽,由于運行時語義的原因,腳本執(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 類型僅在查看如何評估特定語法元素時有用。 實際上,在內(nèi)置函數(shù)的定義中,您永遠不會看到任何其他類型,因為 break / continue / return 不跨函數(shù)邊界工作。
拓展閱讀:§6.2.3 The Completion Record Specification Type
由于 Completion Records 的定義,JavaScript 中的細節(jié)(如冒泡錯誤,直到 try-catch 塊)在規(guī)范中不存在。 事實上,錯誤(或更準確地說是 abrupt completions)是顯式處理的。
沒有任何縮寫,對抽象操作的普通調(diào)用的規(guī)范文本可能會返回一個計算結果或拋出一個錯誤,它看起來像:
下面是一些調(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。 換句話說,會轉發(fā) AbstractOp 中拋出的錯誤,并終止其余的步驟。
Let result be resultCompletionRecord.[[Value]].
注意: 在確保得到 normal completion 后,我們現(xiàn)在可以解構 Completion Record 以得到我們需要的計算的實際結果。
result 就是我們需要的結果。 我們現(xiàn)在可以用它做更多的事情。
這可能會模糊地讓你想起 C 語言中的手動錯誤處理:
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ī)范文本可以用以下兩種等價的方式編寫:
下面的幾個步驟可以調(diào)用一個抽象操作,這個操作可能會拋出 ReturnIfAbrupt:
Let result be AbstractOp().
注意: 這里,就像前面例子中的步驟1一樣,結果是一個 Completion Record.
ReturnIfAbrupt(result).
Note: 注意: ReturnIfAbrupt 通過轉發(fā)來處理任何可能出現(xiàn)的 abrupt completions,并自動將 result 解構到它的 [[Value]]
result 就是我們需要的結果。 我們現(xiàn)在可以用它做更多的事情。
或者,更準確地說,用一個特殊的問號 (?) 符號:
調(diào)用可能帶有問號(?)的抽象操作的幾個步驟 :
Let result be ? AbstractOp().
注意:在這個 notation 中,我們根本不處理 Completion Records 。 ? shorthand 為我們處理了一切事情, 且 result 立馬就可用
result 就是我們需要的結果。 我們現(xiàn)在可以用它做更多的事情。
有時,如果我們知道對 AbstractOp 的特定調(diào)用永遠不會返回一個 abrupt completion,那么它可以向讀者傳達更多關于 spec’s intent。 在這些情況下,一個 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 中,每個 Object 都有一組內(nèi)部方法,規(guī)范的其余部分調(diào)用這些方法來完成某些任務。 所有 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 中找到。
基于這個定義,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)部方法的默認實現(xiàn)。 對于允許外來對象執(zhí)行的操作,有一定的最小限制,但是一般來說,過多的內(nèi)部方法可以執(zhí)行大量的特技操作,而不違反規(guī)范。
Array 對象是這些 exotic objects 的一種。 使用 ordinary objects 可用的工具無法獲取像 Array 對象的 length 屬性的一些特殊語義。其中之一是,設置 Array 對象的 length 屬性可以從對象中刪除屬性,但 length 屬性似乎只是一個普通的數(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)部方法來實現(xiàn)的。 詳見 §9.4.2 Array Exotic Objects
Javascript 對象也可能有定義為包含特定類型值的 internal slots 。 我傾向于將 internal slots 視為甚至對 Object.getOwnPropertySymbols() 都隱藏的以符號命名的屬性。ordinary objects 和 exotic objects 都允許有 internal slots。
在 An internal slot of a JavaScript Object 中, 我提到了大多數(shù)對象都具有的一個名為 [[Prototype]] 的 internal slot 。 (事實上,所有的 ordinary objects ,甚至一些 exotic objects ,比如 Array 對象都有它。) 但是我們也知道有一個叫[[GetPrototypeOf]] 的內(nèi)部方法,我在上面簡要描述過。 它們之間有什么區(qū)別嗎?
這里的關鍵字是 most: 雖然大多數(shù)對象都有 [[Prototype]] internal slot,但所有對象都實現(xiàn) [[GetPrototypeOf]] 內(nèi)部方法。 值得注意的是,Proxy 對象沒有它們自己的 [[Prototype]] ,而且它的 [[GetPrototypeOf]] 內(nèi)部方法遵從已注冊的處理程序或其目標的原型,存儲在 Proxy 對象的 [[ProxyTarget]] 的 internal slot 中。
因此,在處理對象時,引用適當?shù)?internal method 幾乎總是一個好主意,而不是直接查看 internal slot 的值。
另一種思考 Objects, internal methods 和 internal slots 之間關系的方式是通過經(jīng)典的 object-oriented lens。 "Object"類似于指定必須實現(xiàn)的幾個 internal methods 的接口。 ordinary objects 提供了缺省實現(xiàn),exotic objects 可以部分或全部覆蓋。 另一方面,internal slots 似于 Object 的實例變量 —— Object 的實現(xiàn)細節(jié)。
所有這些關系都可以用下面的 UML 圖來概括:
示例: String.prototype.substring()現(xiàn)在我們已經(jīng)很好地理解了規(guī)范是如何組織和編寫的,讓我們開始練習吧!
假設我現(xiàn)在有以下問題:
如果不運行代碼,下面的代碼片段返回什么?
String.prototype.substring.call(undefined, 2, 4)
這是一個相當棘手的問題。 似乎有兩種看似合理的結果:
String.prototype.substring() 可以首先將 undefined 強制轉換為 "undefined" 字符串,然后在該字符串的第二和第三個位置(即間隔 [2,4] )獲取字符得到 "de"。
另一方面,String.prototype.substring() 也可以合理地拋出一個錯誤,從而拒絕未定義的輸入。
不幸的是,當 this 的值不是字符串時,MDN 也沒有真正提供任何關于函數(shù)運行的說明。
在閱讀 algorithm steps 之前,讓我們先想想我們知道什么。 我假設我們已經(jīng)對 str.substring() 的通常工作方式有了基本的了解:即返回給定字符串的一部分。 我們現(xiàn)在真正不確定的是,在 this 值為 undefined 的情況下,它是如何運作的。 因此,我們將特別尋找解決 this 值的 algorithm steps。
幸運的是,String.prototype.substring() algorithm 的第一步專門處理這個值:
Let O be ? RequireObjectCoercible(this value).
? shorthand 允許我們得出這樣的結論: 在某些情況下,RequireObjectCoercible 抽象操作實際上可能會拋出異常,因為否則 ! 會被用來代替。 事實上,如果它拋出一個錯誤,它將與我們上面的第二個假設相對應! 有了希望,我們可以通過單擊超鏈接來了解 RequireObjectCoercible 做了什么。
Requireobjectforecble 抽象操作有點奇怪。 與大多數(shù)抽象操作不同,它是通過表格而不是步驟來定義的:
Argument Type | Result |
---|---|
Undefined 的 | Throw a TypeError exception |
... | ... |
不管怎樣——在對應于 Undefined (我們傳遞給 substring() 的 this 值的類型)的行中,規(guī)范說 RequireObjectCoercible 應該拋出一個異常。 那是因為在 函數(shù)的定義中使用 ? ,我們知道拋出的異常必須冒泡到函數(shù)的調(diào)用方。
這就是我們的答案: 給定的代碼片段會拋出一個 TypeError 異常。
規(guī)范只指定了錯誤拋出的類型,而沒有指定它包含的消息。 這意味著實現(xiàn)可以有不同的錯誤消息,甚至是本地化的錯誤消息。示例: Boolean() 和 String() 會拋出異常嗎?例如,在谷歌的 v86.4(包含在谷歌 Chrome 64中)上,消息是:
TypeError: String.prototype.substring called on null or undefined
而 Mozilla Firefox 57.0提供了一些不那么有用的功能
TypeError: can’t convert undefined to object
與此同時,ChakraCore version 1.7.5.0(Microsoft Edge 中的 JavaScript 引擎)采用了 V8的路線并拋出
TypeError: String.prototype.substring: "this" is null or undefined
在編寫關鍵任務代碼時,必須優(yōu)先考慮異常處理。 因此,"某個內(nèi)置函數(shù)會拋出異常嗎?" 需要仔細琢磨。
在本例中,我們將嘗試回答兩個語言內(nèi)置函數(shù) Boolean() 和 String() 的問題。 我們將只關注對這些函數(shù)的直接調(diào)用,而不是 new Boolean() 和 new String() ——這是 JavaScript 中最不受歡迎的特性 之一,也是幾乎所有 JS 編程指南中最不鼓勵的實踐 YDKJS。
在找到規(guī)范中的 Boolean() 部分之后,我們看到算法似乎相當簡短:
當使用參數(shù)值調(diào)用 Boolean 時,將執(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 這里涉及到一些復雜的基本技巧。 更重要的是,有一個 ? 步驟 3 中的簡寫,可能表明此函數(shù)在某些情況下可能拋出錯誤。 讓我們仔細看看。
步驟1將 value (函數(shù)參數(shù))轉換為布爾值。 有趣的是,沒有一個 ? 或者 !這一步驟的 shorthand ,但通常沒有 Completion Record shorthand 等同于 ! . 因此,步驟 1 不能拋出異常。
第 2 步檢查名為 NewTarget 的東西是否 undefined。 Newtarget 是在 ES2015 中首次添加的 new.target 元屬性的 spec 等價物,它允許規(guī)范區(qū)分 new Boolean() 調(diào)用。 因為我們現(xiàn)在只關注對 Boolean() 的直接調(diào)用,所以我們知道 NewTarget 總是未定義的,并且算法總是直接返回 b,而不需要任何額外的處理。
因為調(diào)用不帶 new 的 Boolean() 只能訪問 Boolean() 算法中的前兩個步驟,而這兩個步驟都不能引發(fā)異常,所以我們得出結論: 無論輸入是什么,Boolean() 都不會引發(fā)異常。
讓我們把注意力轉向 String () :
當使用參數(shù)值調(diào)用 String 時,將執(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īng)驗,我們知道對于我們的案例,NewTarget 總是未定義的,因此可以跳過最后一步。 我們還知道 Type 和 SymbolDescriptiveString 也是安全的,因為它們都不會處理 abrupt completions。 然而,還有一個關于 ? 的問題嗎? 在調(diào)用 ToString 抽象操作之前。 讓我們仔細看看。
就像我們前面看到的 RequireObjectCoercible 一樣,ToString(argument) 也是用一個表定義的:
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 時,value 可以是 Symbol 以外的任何值(在緊接著的步驟中過濾掉)。 然而,還有兩個 ? 對象行中的。 我們可以點擊 ToPrimitive 和 beyond 的鏈接,發(fā)現(xiàn)如果 value 是 Object,那么實際上有很多機會拋出錯誤:
所以對于 String() ,我們的結論是它從不為 primitive values 拋出異常,但可能為 Objects 拋出錯誤。
更多關于 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ī)格
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109679.html
摘要:完整清單是中添加,此處不予介紹布爾值用來表示可能是真或假的值。結果抽象比較運算符在比較它們之前在類型之間進行自動轉換。中的隱式轉換稱為強制類型轉換,并在規(guī)范中定義。這些內(nèi)置類型可用于在不同類型之間進行顯式轉換。 翻譯:瘋狂的技術宅原文:https://www.valentinog.com/bl... 本文首發(fā)微信公眾號:前端先鋒歡迎關注,每天都給你推送新鮮的前端技術文章 show...
摘要:詞法分析對構成源程序的字符流進行掃描然后根據(jù)構詞規(guī)則識別單詞也稱單詞符號或符號。語義分析是編譯過程的一個邏輯階段語義分析的任務是對結構上正確的源程序進行上下文有關性質的審查進行類型審查,審查抽象語法樹是否符合該編程語言的規(guī)則。 1. 文章的內(nèi)容和主題 我對編譯器的深入了解起源于一條推特中的問題:Angular是如何用Angular預先編譯器(AOT)對靜態(tài)代碼進行解析工作的。在進行一些...
摘要:提交內(nèi)容可以是一個提議想法初步描述該階段是對所提交新特性的正式建議。在這個階段需具備以下條件指定一名成員作為審閱通過有實現(xiàn)的或者初步編寫標準,包括問題描述解決方案示例語法語義關鍵的算法及抽象實現(xiàn)在的復雜度等該階段是會出現(xiàn)標準中的第一個版本。 ECMAScript 與 JavaScript ECMAScript 是一套腳本語言的規(guī)范,內(nèi)部編號 ECMA-262 該規(guī)范由 Ecma(Eu...
摘要:提交內(nèi)容可以是一個提議想法初步描述該階段是對所提交新特性的正式建議。在這個階段需具備以下條件指定一名成員作為審閱通過有實現(xiàn)的或者初步編寫標準,包括問題描述解決方案示例語法語義關鍵的算法及抽象實現(xiàn)在的復雜度等該階段是會出現(xiàn)標準中的第一個版本。 ECMAScript 與 JavaScript ECMAScript 是一套腳本語言的規(guī)范,內(nèi)部編號 ECMA-262 該規(guī)范由 Ecma(Eu...
閱讀 2847·2021-09-28 09:36
閱讀 3936·2021-09-22 15:52
閱讀 3630·2021-09-06 15:00
閱讀 1947·2021-09-02 15:40
閱讀 2797·2021-09-02 15:15
閱讀 3454·2021-08-17 10:15
閱讀 2781·2019-08-30 15:53
閱讀 2072·2019-08-29 18:39