翻譯自: How to Read the ECMAScript Specification
Ecmascript 語言規範 The ECMAScript Language specification
(又名:Javascript 規範 the JavaScript specification
或 ECMA-262
)是學習 JavaScript 底層工做原理的很是好的資源。 然而,這是一個龐大的專業文本資料,咋一眼看過去,你們可能會感到迷茫、恐懼,滿懷激情卻無從下手。html
無論你是打算天天閱讀一點 ECMAScript 規範,仍是把它當成一個年度或者季度的目標,這篇文章旨在讓你更輕鬆的開始閱讀最權威的 JavaScript 語言參考資料。node
Ecmascript 規範是全部 JavaScript 運行行爲的權威來源,不管是在你的瀏覽器環境,仍是在服務器環境( Node.js ),仍是在宇航服上[ NodeJS-NASA ] ,或在你的物聯網設備上[ JOHNNY-FIVE ]。 全部 JavaScript 引擎的開發者都依賴於這個規範來確保他們各類天花亂墜的新特性可以其餘 JavaScript 引擎同樣,按預期工做。git
Ecmascript 規範 毫不僅僅對 JavaScript 引擎開發者有用,它對普通的 JavaScript 編碼人員也很是有用,而你只是沒有意識到或者沒有用到。程序員
假設有一天你在工做中發現了下面這個奇怪的問題:github
> 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 #<Set> at Set.add (<anonymous>) > Set.prototype Set {}
而且很是困惑爲何一個方法在它的原型上工做,可是另外一個方法在它的原型上卻不工做。 不幸的是,這種問題你 Google 不到, Stack Overflow 可能也解決不了你的疑惑。web
這個時候你就應該去閱讀 Ecmascript 語言規範。算法
或者,您可能想知道臭名昭著的 loose equality operator (==) 是如何工做的(這裏鬆散地使用了單詞 `"function"[ WAT ])。 做爲一個勤奮好學的程序員,你在 MDN 上查找它 paragraphs of explanation ,卻發現上面的解釋讓你一臉懵逼。編程
這個時候你就應該去閱讀 Ecmascript 語言規範。api
另外一方面,我不建議初次接觸 JavaScript 的開發人員閱讀 ECMAScript 規範。 若是你是 JavaScript 的新手,那麼就開開心心的玩玩 web 吧! 開發一些 web 應用,或者一些基於 Javascript 的攝像頭等。當你踩了足夠多的 JavaScript 坑,或者 JavaScript 問題已經沒法再限制你的開發能力的時候,你就能夠考慮回到這個文檔了。瀏覽器
好了,你如今已經知道,JavaScript 規範在幫助您理解語言或平臺的複雜性方面很是有用。 可是 ECMAScript 規範究竟包含哪些東西呢?
教科書對這個問題的回答是 "ECMAScript 規範中只包含語言特性" 但這並無幫助,由於這就像是在說 "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] |
console
由 Console 標準 定義,而其他部分由 HTML 標準 指定。當你在 Google "ECMAScript specification"時,你會看到不少結果,都聲稱是合法的規範。 你應該讀哪一本?
更有可能的是,在 tc39.github.io/ecma262/
上發佈的規範正是您想要的 ECMA-262。
長話短說:
Ecmascript 語言規範是由一羣來自不一樣背景的人開發的,他們被稱爲 ECMA 國際技術委員會39(或者他們更熟悉的名字 TC39[ TC39])。 TC39 在 TC39.github.io
[ ECMA-262]維護 ECMAScript 語言的最新規範。
讓事情變得複雜的是,每一年 TC39都會選擇一個時間點對規範進行快照,以便成爲當年的 ECMAScript 語言標準,並附帶一個版本號。 例如,ECMAScript 2018語言規範(ECMA-262,第9版) ECMA-262-2018僅僅是2018年6月在 tc39.github.io
[ ECMA-262]中看到的規範,放入歸檔庫中,並進行適當地包裹和 PDFified 以供永久存檔。
正由於如此,除非你但願你的 web 應用程序從 2018 年 6 月開始只能運行在放入 formaldehyde、適當包裝和 PDFified 以便永久存檔的瀏覽器上,不然你應該始終查看 tc39.github.io
[ECMA-262]的最新規範。 可是,若是您但願(或者必須)支持舊版本的瀏覽器或 Node.js 版本,那麼引用舊版本的規範可能會有所幫助。
注: iso/iec 也將 ECMAScript 語言標準發表爲 iso/iec16262[ ISO-16262-2011]。 不過不用擔憂,由於這個標準的文本和 ECMA 國際出版的標準文本徹底同樣,惟一的區別是你必須支付 198 瑞士法郎。
Ecmascript 規範談論了大量的內容。 儘管它的做者盡最大努力把它分割成小的邏輯塊,它仍然是一個超級龐大的文本。
就我我的而言,我喜歡把規格分爲五個部分:
throw a TypeError exception
是什麼意思?)for-in
循環?)var
語句中如何肯定變量名稱?)for-in
循環是如何執行的?)String.prototype.substring()
作什麼?)但規範不是這樣組織的。 相反,它把第一個要點放在 §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 擴展全局對象。
在這一點上,我想指出的是,絕對沒有人從上到下閱讀規範。 相反,只看與你要查找的內容相對應的部分,在該部分中只看您須要的內容。 試着肯定你的具體問題涉及的五大部分中的哪一部分; 若是你沒法肯定哪一部分是相關的,問問本身這樣一個問題:"在何時(不管你想要確認什麼)這個問題被評估了?" 這可能會有幫助。 不要擔憂,操做規範只會在實踐中變得更容易。
Runtime semantics of The Language
和 APIs
的運行時語義是規範中最重要的部分,一般是人們最關心的問題。
總的來講,在規範中閱讀這些章節是很是簡單的。 然而,該規範使用了大量的 shorthands,這些 shorthands 剛剛開始(至少對我來講)是至關討厭的。 我將嘗試解釋其中的一些約定,而後將它們應用到一個一般的工做流程中,以弄清楚幾件事情是如何工做的。
Ecmascript 中的大多數 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 Conventions
你有時會看到相似函數的東西在 spec 中被調用。 Boolean() 函數的第一步是:
當使用參數值調用 Boolean 時,將執行如下步驟:
- Let b be ToBoolean(value).
- ...
這個 ToBoolean
函數被稱爲 abstract operation (抽象操做):它是抽象的,由於它實際上並無做爲一個函數暴露給 JavaScript 代碼。 它只是一個 notation spec writers
(符號規範做者)發明的,讓他們不要寫一樣的東西一遍又一遍。
拓展閱讀: §5.2.1 Abstract Operations
有時候,你可能會看到 [[Notation]] 被用做 "Let proto be obj.[[Prototype]]"。 這個符號在技術上可能意味着幾個不一樣的東西,這取決於它出現的上下文,可是你能夠理解,這個符號指的是一些不能經過 JavaScript 代碼觀察到的內部屬性。
準確地說,它能夠意味着三種不一樣的東西,我將用規範中的例子來講明它們。 不過,你能夠暫時跳過它們。
Ecmascript 規範使用術語 Record
來引用具備固定鍵集的 key-value map
——有點像 C 語言中的結構。 Record 的每一個 key-value
對稱爲 field
。 由於 Records 只能出如今規範中,而不能出如今實際的 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 的具體例子能夠在下一節中找到, §2.4 Completion Records; ? and !
拓展閱讀: §6.2.1 The List and Record Specification Types
Javascript 對象可能有所謂的 internal slots ,規範使用這些 internal slots 來保存數據。 像 Record 字段同樣,這些 internal slots 也不能用 JavaScript 觀察到,可是其中一些可能會經過特定於實現的工具(如 Google Chrome 的 DevTools)暴露出來。 所以,使用 [[Notation]]
來描述 internal slots 也是有意義的。
internal slots 的細節將在 §2.5 JavaScript Objects 中介紹。 如今,不要過於擔憂它們的用途,但請注意下面的例子。
Most 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。 從周圍的環境來看,這一般是至關明顯的。
Javascript 對象也可能有所謂的 internal methods。 像 internal slots 同樣,這些 internal methods 不能經過 JavaScript 直接觀察到。 所以,使用 [[Notation]]
來描述 internal methods 也是有意義的。
internal methods 的細節將在 §2.5 JavaScript Objects 中介紹。 如今,不要過於擔憂它們的用途,但請注意下面的例子。
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]]的第三種意義能夠經過看起來像一個函數調用來區分。
Ecmascript 規範中的每一個運行時語義都顯式或隱式地返回一個報告其結果的 Completion Record
。 這個 Completion Record
是一個Record,有三個可能的領域:
[[Value]]
("what’s returned/thrown")[[Target]]
的標籤,因爲運行時語義的緣由,腳本執行 breaks from/continuesA 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
.
大多數狀況下,您只須要處理 abrupt completions 的 [[ Type ]]
是 throw。 其餘三種 abrupt completion 類型僅在查看如何評估特定語法元素時有用。 實際上,在內置函數的定義中,您永遠不會看到任何其餘類型,由於 break / continue / return 不跨函數邊界工做。
拓展閱讀: §6.2.3 The Completion Record Specification Type
因爲 Completion Records 的定義,JavaScript 中的細節(如冒泡錯誤,直到 try-catch
塊)在規範中不存在。 事實上,錯誤(或更準確地說是 abrupt completions)是顯式處理的。
沒有任何縮寫,對抽象操做的普通調用的規範文本可能會返回一個計算結果或拋出一個錯誤,它看起來像:
下面是一些調用抽象操做的步驟,這些步驟能夠拋出
without any shorthands
的操做:
- Let resultCompletionRecord be AbstractOp().
Note: resultCompletionRecord is a Completion Record.
- If resultCompletionRecord is an abrupt completion, return resultCompletionRecord.
注意: 在這裏,若是是 abrupt completion,則直接返回 resultCompletionRecord。 換句話說,會轉發 AbstractOp 中拋出的錯誤,並終止其他的步驟。
- Let result be resultCompletionRecord.[[Value]].
注意: 在確保獲得 normal completion 後,咱們如今能夠解構 Completion Record 以獲得咱們須要的計算的實際結果。
- result 就是咱們須要的結果。 咱們如今能夠用它作更多的事情。
這可能會模糊地讓你想起 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 規範的編輯器增長了一些縮寫。 自 ES2016 以來,一樣的規範文本能夠用如下兩種等價的方式編寫:
下面的幾個步驟能夠調用一個抽象操做,這個操做可能會拋出
ReturnIfAbrupt
:
- Let result be AbstractOp().
注意: 這裏,就像前面例子中的步驟1同樣,結果是一個 Completion Record.
- ReturnIfAbrupt(result).
Note: 注意:
ReturnIfAbrupt
經過轉發來處理任何可能出現的 abrupt completions,並自動將 result 解構到它的[[Value]]
- result 就是咱們須要的結果。 咱們如今能夠用它作更多的事情。
或者,更準確地說,用一個特殊的問號
(?)
符號:調用可能帶有問號(?)的抽象操做的幾個步驟 :
- Let result be ? AbstractOp().
注意:在這個 notation 中,咱們根本不處理
Completion Records
。?
shorthand 爲咱們處理了一切事情, 且 result 立馬就可用
- result 就是咱們須要的結果。 咱們如今能夠用它作更多的事情。
有時,若是咱們知道對 AbstractOp 的特定調用永遠不會返回一個 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 Shorthands
在 ECMAScript 中,每一個 Object 都有一組內部方法,規範的其他部分調用這些方法來完成某些任務。 全部 object 都有的一些內部方法是:
詳盡的列表可在 §6.1.7.2 Object Internal Methods and Internal Slots 中找到。
基於這個定義,function objects (or just "functions") 是簡單的對象,它們附加了 [[Call]] 內部方法,可能還有[[ Construct ]]內部方法; 所以,它們也被稱爲 callable objects。
而後,規範將全部 Object 分爲兩類: ordinary objects
和 exotic objects
。 你遇到的大多數對象都是 ordinary objects,這意味着它們全部的內部方法都是在 §9.1 Ordinary Object Internal Methods and Internal Slots。
然而,ECMAScript 規範還定義了一些 exotic objects,這些對象能夠覆蓋這些內部方法的默認實現。 對於容許外來對象執行的操做,有必定的最小限制,可是通常來講,過多的內部方法能夠執行大量的特技操做,而不違反規範。
Array 對象是這些 exotic objects 的一種。 使用 ordinary objects 可用的工具沒法獲取像 Array 對象的 length 屬性的一些特殊語義。其中之一是,設置 Array 對象的 length 屬性能夠從對象中刪除屬性,但 length 屬性彷佛只是一個普通的數據屬性。 相比之下,
new Map().size
只是Map.prototype
上指定的 getter 函數,不具備相似於[].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]]
內部方法來實現的。 詳見 §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
中, 我提到了大多數對象都具備的一個名爲 [[Prototype]]
的 internal slot
。 (事實上,全部的 ordinary objects ,甚至一些 exotic objects ,好比 Array 對象都有它。) 可是咱們也知道有一個叫[[GetPrototypeOf]]
的內部方法,我在上面簡要描述過。 它們之間有什麼區別嗎?
這裏的關鍵字是 most
: 雖然大多數對象都有 [[Prototype]]
internal slot,但全部對象都實現 [[GetPrototypeOf]]
內部方法。 值得注意的是,Proxy 對象沒有它們本身的 [[Prototype]]
,並且它的 [[GetPrototypeOf]]
內部方法聽從已註冊的處理程序或其目標的原型,存儲在 Proxy 對象的 [[ProxyTarget]]
的 internal slot 中。
所以,在處理對象時,引用適當的 internal method
幾乎老是一個好主意,而不是直接查看 internal slot
的值。
另外一種思考 Objects
, internal methods
和 internal slots
之間關係的方式是經過經典的 object-oriented lens
。 "Object"相似於指定必須實現的幾個 internal methods
的接口。 ordinary objects
提供了缺省實現,exotic objects
能夠部分或所有覆蓋。 另外一方面,internal slots
似於 Object
的實例變量 —— Object 的實現細節。
全部這些關係均可以用下面的 UML 圖來歸納:
如今咱們已經很好地理解了規範是如何組織和編寫的,讓咱們開始練習吧!
假設我如今有如下問題:
若是不運行代碼,下面的代碼片斷返回什麼?
String.prototype.substring.call(undefined, 2, 4)
這是一個至關棘手的問題。 彷佛有兩種看似合理的結果:
undefined
強制轉換爲 "undefined"
字符串,而後在該字符串的第二和第三個位置(即間隔 [2,4] )獲取字符獲得 "de"
。String.prototype.substring()
也能夠合理地拋出一個錯誤,從而拒絕未定義的輸入。不幸的是,當 this 的值不是字符串時,MDN 也沒有真正提供任何關於函數運行的說明。
在閱讀 algorithm steps 以前,讓咱們先想一想咱們知道什麼。 我假設咱們已經對 str.substring()
的一般工做方式有了基本的瞭解:即返回給定字符串的一部分。 咱們如今真正不肯定的是,在 this
值爲 undefined
的狀況下,它是如何運做的。 所以,咱們將特別尋找解決 this 值的 algorithm steps
。
幸運的是,String.prototype.substring() algorithm 的第一步專門處理這個值:
- Let O be ? RequireObjectCoercible(this value).
?
shorthand 容許咱們得出這樣的結論: 在某些狀況下,RequireObjectCoercible 抽象操做實際上可能會拋出異常,由於不然 !
會被用來代替。 事實上,若是它拋出一個錯誤,它將與咱們上面的第二個假設相對應! 有了但願,咱們能夠經過單擊超連接來了解 RequireObjectCoercible 作了什麼。
Requireobjectforecble
抽象操做有點奇怪。 與大多數抽象操做不一樣,它是經過表格而不是步驟來定義的:
Argument Type | Result |
---|---|
Undefined 的 | Throw a TypeError exception |
... | ... |
無論怎樣——在對應於 Undefined
(咱們傳遞給 substring()
的 this 值的類型)的行中,規範說 RequireObjectCoercible 應該拋出一個異常。 那是由於在 函數的定義中使用 ?
,咱們知道拋出的異常必須冒泡到函數的調用方。
這就是咱們的答案: 給定的代碼片斷會拋出一個 TypeError
異常。
規範只指定了錯誤拋出的類型,而沒有指定它包含的消息。 這意味着實現能夠有不一樣的錯誤消息,甚至是本地化的錯誤消息。例如,在谷歌的 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
在編寫關鍵任務代碼時,必須優先考慮異常處理。 所以,"某個內置函數會拋出異常嗎?" 須要仔細琢磨。
在本例中,咱們將嘗試回答兩個語言內置函數 Boolean()
和 String()
的問題。 咱們將只關注對這些函數的直接調用,而不是 new Boolean()
和 new String()
——這是 JavaScript 中最不受歡迎的特性 之一,也是幾乎全部 JS 編程指南中最不鼓勵的實踐 YDKJS。
在找到規範中的 Boolean() 部分以後,咱們看到算法彷佛至關簡短:
當使用參數值調用 Boolean 時,將執行如下步驟:
- 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 中的簡寫,可能代表此函數在某些狀況下可能拋出錯誤。 讓咱們仔細看看。
步驟1將 value (函數參數)轉換爲布爾值。 有趣的是,沒有一個 ?
或者 !
這一步驟的 shorthand ,但一般沒有 Completion Record shorthand 等同於 !
. 所以,步驟 1 不能拋出異常。
第 2 步檢查名爲 NewTarget 的東西是否 undefined。 Newtarget
是在 ES2015 中首次添加的 new.target 元屬性的 spec 等價物,它容許規範區分 new Boolean()
調用。 由於咱們如今只關注對 Boolean()
的直接調用,因此咱們知道 NewTarget
老是未定義的,而且算法老是直接返回 b,而不須要任何額外的處理。
由於調用不帶 new
的 Boolean()
只能訪問 Boolean()
算法中的前兩個步驟,而這兩個步驟都不能引起異常,因此咱們得出結論: 不管輸入是什麼,Boolean()
都不會引起異常。
讓咱們把注意力轉向 String () :
當使用參數值調用 String 時,將執行如下步驟:
- 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%"
)).
根據咱們使用 Boolean() 函數進行同類分析的經驗,咱們知道對於咱們的案例,NewTarget 老是未定義的,所以能夠跳過最後一步。 咱們還知道 Type 和 SymbolDescriptiveString 也是安全的,由於它們都不會處理 abrupt completions。 然而,還有一個關於 ?
的問題嗎? 在調用 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() 中調用 ToString 時,value 能夠是 Symbol
之外的任何值(在緊接着的步驟中過濾掉)。 然而,還有兩個 ?
對象行中的。 咱們能夠點擊 ToPrimitive 和 beyond 的連接,發現若是 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));
到目前爲止,咱們只分析了 API 函數,讓咱們嘗試一些不一樣的東西。
未完待續 https://github.com/TimothyGu/...