如何阅读 ECMAScript 标准

Last updated: 2022-06-30
JavaScript
Slides

如何阅读 ECMAScript 规范

前言

查询 JavaScript 语言某个 API 或特性

  • 搜索引擎
  • MDN

MDN 关于JavaScript 参考的介绍中提及:

JavaScript 核心语言特性的文档 (绝大部分基于 ECMAScript )

在 大多数概念及 API 的末尾,MDN 文档也同样会附上规范的链接。

标准化之前

1995 年,网景和 Sun 共同完成 LiveScript 的开发,就在 Netscape 14 Navigator 2 正式发布前,网景把 LiveScript 改名为 JavaScript,以便搭上媒体当时热烈炒作 Java 的顺风车。

日后,网景又在 NetscapeNavigator3 中发布了 1.1 版本。这时候,微软决定向 IE 投入更多资源。就在 NetscapeNavigator3 发布后不久,微软发布了 IE3,其中包含自己名为 JScript(叫这个名字是为了避免与网景发生许可纠纷)的 JavaScript 实现。

微软的动作意味着出现了两个版本的 JavaScript: NetscapeNavigator 中的 JavaScript,以及 IE 中的 JScript。而当时 JavaScript 还没有规范其语法或特性的标准,两个版本并存让这个问题更加突出了。随着业界担忧日甚,JavaScript 终于踏上了标准化的征程。

编写规范

1997 年,JavaScript1.1 作为提案被提交给欧洲计算机制造商协会(Ecma)。第 39 技术委员会(TC39)承担了“标准化一门通用、跨平台、厂商中立的脚本语言的语法和语义”的任务(参见 TC39-ECMAScript)。

TC39(Ecma 技术委员会(Technical Committee)使用数字来标记旗下的技术委员会,当时下一个可用数字是 39) 委员会由来自网景、Sun、微软、Borland、Nombas 和其他对这门脚本语言有兴趣的公司的工程师组成。他们花了数月时间打造出 ECMA-262,也就是 ECMAScript(发音为“ek-ma-script”)这个新的脚本语言标准。

更多 JavaScript 发展历史可以参考JavaScript 二十年

规范总览

  • 第 1-4 章描述了规范的背景及大纲。
  • 第 5 章定义了规范中的一些符号或者语法的约定。
  • 第 6-10 章定义了 ECMAScript 程序操作包含的执行环境(数据类型、抽象操作、语法)。
  • 第 11-17 章定义了实际的 ECMAScript 语言,包括它的语法编码以及语言特性的执行语义。
  • 第 18-28 章定义了 ECMAScript 标准库。它们包括所有当 ECMAScript 程序执行时可用的标准对象的定义。
  • 第 29 章描述了访问备用数组缓冲区内存和原子对象的内存一致性模型。

你将了解到

  1. Algorithm Steps 算法步骤
  2. Abstract Operations 抽象操作
  3. Internal slots & Internal methods 内部槽 内部方法
  4. List & Record
  5. Completion Record
  6. Grammar Notation 语法标注

1. Algorithm Steps

通常使用带编号的列表来指定算法的步骤。

算法步骤可以细分为多个连续的子步骤。子步骤需要用缩进表示,可以进一步被划分成缩进的子步骤。

1. Top-level step
   a. Substep.
   b. Substep.
      i. Subsubstep.
         1. Assert: Conclusion
            a. Subsubsubsubstep
               i. Subsubsubsubsubstep

例:Number 规范

Number(value)

  1. 如果提供了 value,计算出数字化后的 value
    1. prim 为数值化后的数字
    2. 如果 prim 是 BigInt 类型,则将 n 等于数值部分
    3. 否则,n 等于 prim
  2. 如果 value 没有提供,则值为 +0
  3. 如果没有使用 new,则返回 n
  4. 否则,生成新的对象 O,将 O 的内部属性设置为 n,返回 O

2. Abstract Operations

规范使用一种叫 Abstract Operation 抽象操作 的概念,它将需要复用的逻辑单独抽出来,形成一套可复用的步骤。

These operations are not a part of the ECMAScript language; they are defined here solely to aid the specification of the semantics of the ECMAScript language.

这些操作不是 ECMAScript 语言的一部分;在这里定义它们只是为了帮助规范 ECMAScript 语言的语义。

例:ToNumeric

  1. 如果 input 是 Object 类型,ToPrimitive(value, number)将其转换成 preferredType 指定的基本类型,否则如果不是 Object 类型,直接返回 input
  2. 如果 value 是 BigInt,则返回 BigInt 的数值
  3. 否则,交给 ToNumber 处理

例:toNumber

  1. 将参数按照表格对应的情况进行转换
    • 𝔽(x) 的意思是 数值化的 x

3. Internal slots, Internal methods

O.[[V]] 通常用于以下三种情况:

  1. Record fields: 类似 Key-Value 对象

  2. Internal slots: 存在于对象上,用于记录状态、数据。

    例:Set O.[[NumberData]] to n.

  3. Internal methods: 存在于对象上,定义自身的行为逻辑的一种算法。

    例:Return ? O.[[Get]](P, O).

4. List 和 Record

  • List: « 1, 2 »
  • Record: { [[Field1]]: 42, [[Field2]]: false, [[Field3]]: empty }
    • Completion Record:

      { [[Type]]: continue, [[Value]]: empty, [[Target]]: empty }.

    • Reference Record

      { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }

5. 特定的 Record: Completion Record

有如下字段

  • [[Type]]
  • [[Value]]
  • [[Target]]

字段的说明:

  • 结束发生的完成类型 [[Type]]: normal,break,continue,return,throw
  • 如果 [[Type]] 是 normal, return, throw, 则通过 [[Value]] 记录是生成正常值或者抛出的异常值,否则就是 empty
  • 如果 [[Type]] 是 break, continue, 可以选择性的提供 [[Target]] 表明控制流要转移的目标 label

假设我们需要调用 ToNumeric 的抽象操作,需要获取操作结果 val,往往规范会这样描述:

  1. 令 result 为 ToNumeric() 的结果
  2. 如果 result 是 abrupt completion(参数不合法,报错),则直接返回 result
    • abrupt completion refers to any Completion Record with a [[Type]] value other than normal.
  3. 读取 result.[[Value]] 的值,赋值给 val
  4. 返回 val

规范将上述逻辑用ReturnIfAbrupt简写。表达上述含义,即:

如果是 abrupt completion 就直接返回它,若是 normal completion 就取出其中的 [[Value]]」。

执行抽象操作会生成 Completion Record,因此也就相当于是 ReturnIfAbrupt(ToNumeric())

规范提供了更简单的写法

  • ? ToNumeric() 等价于 ReturnIfAbrupt(ToNumeric())
  • ! ToNumeric(),断言拿到的结果一定不是 abrupt completion(类似 TypeScript 中的 !)

6. 语法标注

WhileStatement :
   while ( Expression ) Statement
代号 :
   具体语法

ArgumentList :
   AssignmentExpression
   ArgumentList , AssignmentExpression
代号 :
   语法1
   语法2

参数化

StatementList[Return, In]:
   ReturnStatement[?Return]
   ExpressionStatement


StatementList:
   ReturnStatement[~In]
   ExpressionStatement[+In] opt

StatementList[Return]:
   [+Return]ReturnStatement
   [-Return]ExpressionStatement

总结

  1. ECMAScript 规范是语言的定义,不是语言的具体实现
  2. 规范通过 Abstract Operations 抽象类似的功能,通过 Algorithm Steps 阐述一条规范细则的逻辑步骤
  3. 规范内的数据通过基本类型、List 和 Record 表示,并且有一些特殊的 Record 以帮助规范处理常见的特殊场景
  4. 规范通过语法标注约定了代码的语法

案例

  1. 从 ECMAScript 规范解读 this
  2. at()
  3. typeof
  4. Arrow Function

at(index)

console.log([1, 2, 3].at(1))
console.log([1, 2, 3].at(-1))
console.log([1, 2, 3].at(1.5))
console.log([1, 2, 3].at(4))
console.log([1, 2, 3].at(NaN))
  1. 使 O 等于 ToObject(this) ,对于数组已经是 Object 的情况会直接返回 this 给 O
  2. 通过 O 的 length 属性获取 O 的长度 len
  3. 使 relativeIndex 等于 index 数字整数化的结果
    1. If number is NaN, +0𝔽, or -0𝔽, return 0.
    2. Let integer be floor(abs(ℝ(number))).
  4. 如果 relativeIndex 是正数,则 k 等于 relativeIndex,否则 k 等于 len+relativeIndex(等于减去 relativeIndex)
  5. 如果 k < 0 或 k ≥ len,返回 undefined
  6. 通过 Get 获取 O 对象对应键的值

typeof

UnaryExpression : typeof UnaryExpression

  1. 使 val 等于 UnaryExpression 的执行结果
  2. 如果 val 是 Reference Record (知乎),判断其[[base]]如果是 unresolved,返回 undefined(某种特殊情况)
  3. 否则,通过 GetValue 获取 val
    1. 这里跳过 val 不是 Reference Record 的话,直接返回值
    2. 如果是 Reference Record,通过 V.[[ReferencedName]] 获取值
  4. 如果 Type(val) 是 Object,并且 val 有 [[IsHTMLDDA]] 属性,返回 "undefined"(仅有 document.all 包含 [[IsHTMLDDA]]。)
  5. 根据表格返回 val 对应的 type 类型

Arrow Function

Early Errors

let C = () => 1
let CA = (a = C()) => { console.log(a) }
let B = async () => 1
let BA = (a = await B()) => {console.log(a) }

let A = ({}) => {
  'use strict'
  console.log('A')
}
  1. 出现 yield await 语句会报错
  2. FunctionBody 使用 strict 模式,
  3. 参数与方法内部声明的变量名重复

Arrow Function

  1. If name is not present, set name to "".
  2. Let env be the LexicalEnvironment of the running execution context.
  3. Let privateEnv be the running execution context's PrivateEnvironment.
  4. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, ArrowParameters, ConciseBody, lexical-this, env, privateEnv).

Function

  1. If name is not present, set name to "".
  2. Let env be the LexicalEnvironment of the running execution context.
  3. Let privateEnv be the running execution context's PrivateEnvironment.
  4. Let closure be OrdinaryFunctionCreate(%Function.prototype%, sourceText, FormalParameters, FunctionBody, non-lexical-this, env, privateEnv).

OrdinaryFunctionCreate

  1. If thisMode is lexical-this, set F.[[ThisMode]] to lexical.
  2. Else if Strict is true, set F.[[ThisMode]] to strict.
  3. Else, set F.[[ThisMode]] to global.

Table 33

lexical: 与词法封闭函数一致
strict: 与函数调用一致
global: 对全局对象的引用

参考

  1. ECMAScript® Specification
  2. How to Read the ECMAScript Specification
  3. ECMAScript 阅读指南(二)
  4. Understanding ECMAScript