2020年9月

翻译本文的目的是尝试给出ECMAScript规范中核心术语的译法,供同好品评。

原文链接:https://timothygu.me/es-howto/

摘要

ECMAScript语言规范(又称JavaScript规范或ECMA-262)是学习JavaScript复杂工作原理的一手资料。然而,其浩繁的卷帙一开始总会让人无从下手、望而生畏。本文旨在降低阅读这一最佳JavaScript语言参考的阅读门槛。

目录

1. 前言
1.1 为什么应该阅读ECMAScript规范
1.2 什么属于ECMAScript规范,什么不属于
1.3 先别急,ECMAScript规范在哪里?
1.4 查阅规范

2. 运行时语义
2.1 算法步骤
2.2 抽象操作
2.3 什么是[This]
2.3.1 Record的字段
2.3.2 JavaScript对象的内部栏位
2.3.3 JavaScript对象的内部方法
2.4 完成记录:?!
2.5 JavaScript对象
2.6 示例:String.prototype.substring()
2.7 示例:Boolean()String()可以抛异常吗?
2.8 示例:typeof操作符

术语表
常见抽象操作

参考
参考资料

1. 前言

你肯定知道,每天读一点ECMAScript规范有益健康。无论它是你新年的小目标,还是仅仅为了遵照医嘱,反正欢迎阅读本文!

注意:本文只在指称规范是使用“ECMAScript”,其他地方使用“JavaScript”。不过,这两个词指的是同一个东西。(历史上,ECMAScript和JavaScript有一些区别,不过那不是本文的重点,大家可以自行搜索。)

1.1 为什么应该阅读ECMAScript规范

ECMAScript规范是实现JavaScript的权威资料,不管是在浏览器中,在Node.js中,还是在IoT设备中。所有JavaScript引擎的开发者都依靠这份文档确保自己的新特性能够跟其他JavaScript引擎一样如期工作。

不过必须得说明一下,规范的效用绝对不仅仅限于被称为“JavaScript引擎开发者”的那些神秘人物。实际上,规范对你我这样的普通JavaScript程序员一样有用,只是你还不知道罢了。

假如有一天你发现了下面这个问题:

> 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 {}

并且想不通为什么一个方法可以对自己的原型起作用,而另一个方法却不能对自己的原型起作用。可惜谷歌总是在你最需要帮助时失灵,而一直给力的Stack Overflow居然也无能为力。

看规范可以找到答案。

或者,你可能就是想知道臭大街的松散相等操作符(==)到底怎么起作用。作为曾经勤奋好学的软件工程师,你在MDN上查到几段解释,但看来看去只会越来越晕。

看规范可以找到答案。

话说回来,我也不推荐JavaScript新手看ECMAScript规范。如果你才开始接触JavaScript,那还是先写几个网页或者Web应用吧!要不就写个基于JavaScript的保姆摄像头!别的什么东西也行!等你体会到足够多的JavaScript缺点,或者已经足够有钱而不必再担心JavaScript了,再回来看这篇文章不迟。

好啦,现在大家知道规范对于理解语言或平台的工作机制非常有帮助了。但要阅读ECMAScript规范,到底应该从哪里入手呢?

1.2 什么属于ECMAScript规范,什么不属于

1.3 先别急,ECMAScript规范在哪里?

谷歌“ECMAScript规范”,会看到很多结果,都说是合法规范。那应该看哪一个?

长话短说,tc39.es/ecma262/这个规范最可能是你想看的。

ECMAScript语言规范由具有不同背景的一群人共同制定,这群人就是TC39(即Ecma International Technical Committee 39)。TC39在tc39.es上面维护着ECMAScript语言的最新规范。

问题在于,TC39每年都会在某个时间点将规范的一个快照变成该年度的ECMAScript Language标准,并给它分配一个版本号。比如,ECMAScript® 2019 Language Specification (ECMA-262, 10th edition)(常被称为ES10或ES2019)就是2019年6月份在tc39.es上看到的规范,把它放福尔马林里那么一泡,一塑封并给出个PDF版,就永久封存了。

为此,除非你只想让自己的Web应用在2019年6月份之前发布的浏览器上跑,否则就应该只看tc39.es上最新的规范。但如果你想(或必须)支持旧版本浏览器或Node.js,那可能老版本规范有用。

ISO/IEC也把ECMAScript语言标准重新发布为ISO/IEC 22275。不过别担心,因为该标准基本上就是一个指向ECMAScript规范的超链接。

1.4 查阅规范

ECMAScript规范涉及的内容极多。即使规范的作者全力将它分成有逻辑的部分,仍然可以用卷帙浩繁来形容。

从个人角度,我喜欢将规范分成五部分。

  • 约定与基础(“什么是Nubmer?规范中说「抛出TypeError异常」时意味着什么?”)
  • 语言的文法产生式(“程序员应该怎么写for-in循环?”)
  • 语言的静态语义(“怎么确定var语句中的变量名?”)
  • 语言的运行时语义(如何执行for-in循环?
  • API(“String.prototype.substring()能做什么?”)

不过规范并不是这么组织的。事实上,它把第一个项目符号放在了“§5 表示方式约定”到“§9 普通与异质对象行”中,接下来三个项目符号穿插放在了“§10 ECMAScript语言:源代码”到“§15 ECMAScript语言:脚本与模块”中,比如:

  • §13.6 if语句文法产生式

    • §13.6.1-6 静态语义
    • §13.6.7 运行时语义
  • §13.7 迭代语句文法产生式

    • §13.7.1 共享的静态和运行时语义
    • §13.7.2 do-while语句

      • §13.7.2.1-5 静态语句
      • §13.7.2.6 运行时语义
    • §13.7.3 while语句
    • ……

而AP则分布在条款“§13 全局对象”到“§27 反射”中。

现在,我想说明一下,绝对没有人从头到尾地读规范。而是只会看与自己想寻找的答案相关的部分,而在该部分中则只看那些需要的地方。因此要学会判断你的问题与上述五大部分中的哪些相关。如果想不出来,可以问自己一个问题:“这个(你想确认的东西)是什么时间被求值的?”,应该有帮助。别担心,查规范查得越多就会越容易的。

2. 运行时语义

语言和API的运行时语义是规范中最大一部分,通常也是人们问题最多的地方。

总体来看,规范中这些部分都很直观明了。不过,规范使用的很多简写形式,让刚开始阅读的人(至少让我)感到很讨厌。接下来会解释一些这种约定,然后通过分析几个特性的原理来看看怎么使用它们。

2.1 算法步骤

ECMAScript中的大多数运行时语义都是通过一系列算法步骤规定的,不像伪代码,但更精确。

示例1

算法步骤的示例:

  1. a为1;
  2. ba+a
  3. b为2,则

    1. 哇!没算错。
  4. 否则

    1. 去!

继续阅读:“§5.2 算法约定”

2.2 抽象操作

有时候,你会看到一个类似函数调用的东西。Boolean()函数的第一步是:

2.3 什么是[This]

2.3.1 Record的字段

2.3.2 JavaScript对象的内部栏位

2.3.3 JavaScript对象的内部方法

2.4 完成记录:?!

2.5 JavaScript对象

2.6 示例:String.prototype.substring()

2.7 示例:Boolean()String()可以抛异常吗?

2.8 示例:typeof操作符

翻译本文的目的是尝试给出ECMAScript规范中核心术语的译法,供同好品评。

原文链接:https://v8.dev/blog/understanding-ecmascript-part-4

环球同此凉热

Mozilla的Jason Orendorff写了一篇深入分析JS诡异语法的文章。虽然实现细节上有差异,但每个JS引擎在这些诡异的细节上都会面对同样的问题。

包含文法

这篇文章将深入探讨包含文法(cover grammar)。包含文法是为那些乍一看模棱两可的语法构造规定文法的一种方式。

为简单起见,我们跳过下标[In, Yield, Await],因为对本文不重要。可以参考第三篇文章,了解它们的含义和用法。

有限前查

通常,解析器在有限前查(finite lookhead,跟进固定个数的标记)基础上决定使用哪个产生式。

有时候,下一个标记可以毫无歧义地决定要使用的产生式。例如

UpdateExpression :
  LeftHandSideExpression
  LeftHandSideExpression ++
  LeftHandSideExpression --
  ++ UnaryExpression
  -- UnaryExpression

- 阅读剩余部分 -

翻译本文的目的是尝试给出ECMAScript规范中核心术语的译法,供同好品评。

原文链接:https://v8.dev/blog/understanding-ecmascript-part-3

这一次我们深入ECMAScript语言及其语法的定义。如果你不太熟悉上下文无关文法,应该先补补课,至少先弄懂一些基本概念。因为规范中使用了上下文无关文法定义语言。

ECMAScript文法

ECMAScript规范定义了4种文法。

  1. 词法文法:描述怎么把Unicode码点(code point)翻译为输入元素(标记、行终止符、注释、空白)序列。
  2. 语法文法:定义标记(token)怎么构成语法正确的程序。
  3. 正则文法:描述怎么把Unicode码点翻译为正则表达式。
  4. 数值字符串文法:描述怎么把String翻译成数字值。

每种文法都用上下文无关文法来定义,都包含一组产生式。

不同的文法使用了不同的表示方式。语法文法表示为LeftHandSideSymbol :,词法文法和正则文法表示为LeftHandSideSymbol ::,而数值字符串文法表示为LeftHandSideSymbol :::。(以冒号的多少来区分。——译者注)

接下来我们详细分析一下词法文法和语法文法。

- 阅读剩余部分 -

翻译本文的目的是尝试给出ECMAScript规范中核心术语的译法,供同好品评。

原文链接:https://v8.dev/blog/understanding-ecmascript-part-2

要理解规范,可以拿一个我们知道的JavaScript特性,看看它是怎么规定的。

注意,本文包含从2020年2月的ECMAScript规范中复制的算法,请以正式规范为准。

我们知道,访问对象的属性需要走查(walk)原型链。如果对象上没有要读的属性,就会沿原型链逐级查找,直到找到这个属性(或者找到一个没有原型的对象)。(这个过程我们可以称其为原型链走查或走查原型链。——译者注)

比如:

const o1 = { foo: 99 };
const o2 = {};
Object.setPrototypeOf(o2, o1);
o2.foo;
// → 99

这种原型走查是在哪里定义的?

最好的起点是对象内部方法

有两个与查找属性相关的内部方法:[[GetOwnProperty]][[Get]]。我们感兴趣的是不限制自有(own)属性的,所以就搜[[Get]]吧。

可是,属性描述符规范类型(Property Descriptor)也有一个字段叫[[Get]]。因此搜索的时候要注意从它们不同的用法来区分。

- 阅读剩余部分 -

翻译本文的目的是尝试给出ECMAScript规范中核心术语的译法,供同好品评。

原文链接:https://v8.dev/blog/understanding-ecmascript-part-1

前言

即便你懂JavaScript,阅读其规范也会让人畏缩。

让我们从一个具体的例子开始,然后通过规范去理解它。下面的代码演示了Object.prototype.hasOwnProperty的用法:

const o = { foo: 1 };
o.hasOwnProperty('foo'); // true
o.hasOwnProperty('bar'); // false

o并没有一个叫hasOwnProperty的属性,因此要沿原型链向上查找。于是,在o的原型Object.prototype上找到了它。

为描述Object.prototype.property的工作原理,规范使用了类似伪代码的说明:

Object.prototype.hasOwnProperty(V)

在以参数V调用hasOwnProperty方法时,将执行以下步骤:

  1. P? ToPropertyKey(V)
  2. O? ToObject(this值)
  3. 返回? HasOwnProperty(O, P)

以及

- 阅读剩余部分 -