概述
规格文件是计算机语言的官方标准,详细描述语法规则和实现方法。
一般来说,没有必要阅读规格,除非你要写编译器。因为规格写得非常抽象和精炼,又缺乏实例,不容易理解,而且对于解决实际的应用问题,帮助不大。但是,如果你遇到疑难的语法问题,实在找不到答案,这时可以去查看规格文件,了解语言标准是怎么说的。规格是解决问题的“最后一招”。
这对 JavaScript 语言很有必要。因为它的使用场景复杂,语法规则不统一,例外很多,各种运行环境的行为不一致,导致奇怪的语法问题层出不穷,任何语法书都不可能囊括所有情况。查看规格,不失为一种解决语法问题的最可靠、最权威的终极方法。
本章介绍如何读懂 ECMAScript 6 的规格文件。
ECMAScript 6 的规格,可以在 ECMA 国际标准组织的官方网站(www.ecma-international.org/ecma-262/6.0/)免费下载和在线阅读。
术语
ES6 规格使用了一些专门的术语,了解这些术语,可以帮助你读懂规格。本节介绍其中的几个。
抽象操作
所谓“抽象操作”(abstract operations)就是引擎的一些内部方法,外部不能调用。规格定义了一系列的抽象操作,规定了它们的行为,留给各种引擎自己去实现。
举例来说,Boolean(value)
的算法,第一步是这样的。
- Let
b
beToBoolean(value)
.
这里的ToBoolean
就是一个抽象操作,是引擎内部求出布尔值的算法。
许多函数的算法都会多次用到同样的步骤,所以 ES6 规格将它们抽出来,定义成“抽象操作”,方便描述。
Record 和 field
ES6 规格将键值对(key-value map)的数据结构称为 Record,其中的每一组键值对称为 field。这就是说,一个 Record 由多个 field 组成,而每个 field 都包含一个键名(key)和一个键值(value)。
[[Notation]]
ES6 规格大量使用[[Notation]]
这种书写法,比如[[Value]]
、[[Writable]]
、[[Get]]
、[[Set]]
等等。它用来指代 field 的键名。
举例来说,obj
是一个 Record,它有一个Prototype
属性。ES6 规格不会写obj.Prototype
,而是写obj.[[Prototype]]
。一般来说,使用[[Notation]]
这种书写法的属性,都是对象的内部属性。
所有的 JavaScript 函数都有一个内部属性[[Call]]
,用来运行该函数。
F.[[Call]](V, argumentsList)
上面代码中,F
是一个函数对象,[[Call]]
是它的内部方法,F.[[call]]()
表示运行该函数,V
表示[[Call]]
运行时this
的值,argumentsList
则是调用时传入函数的参数。
Completion Record
每一个语句都会返回一个 Completion Record,表示运行结果。每个 Completion Record 有一个[[Type]]
属性,表示运行结果的类型。
[[Type]]
属性有五种可能的值。
normal
return
throw
break
continue
如果[[Type]]
的值是normal
,就称为 normal completion,表示运行正常。其他的值,都称为 abrupt completion。其中,开发者只需要关注[[Type]]
为throw
的情况,即运行出错;break
、continue
、return
这三个值都只出现在特定场景,可以不用考虑。
抽象操作的标准流程
抽象操作的运行流程,一般是下面这样。
- Let
result
beAbstractOp()
.
- If
result
is an abrupt completion, returnresult
.
- Set
result
toresult.[[Value]]
.
- return
result
.
上面的第一步调用了抽象操作AbstractOp()
,得到result
,这是一个 Completion Record。第二步,如果result
属于 abrupt completion,就直接返回。如果此处没有返回,表示result
属于 normal completion。第三步,将result
的值设置为resultCompletionRecord.[[Value]]
。第四步,返回result
。
ES6 规格将这个标准流程,使用简写的方式表达。
- Let
result
beAbstractOp()
.
ReturnIfAbrupt(result)
.
- return
result
.
这个简写方式里面的ReturnIfAbrupt(result)
,就代表了上面的第二步和第三步,即如果有报错,就返回错误,否则取出值。
甚至还有进一步的简写格式。
- Let
result
be? AbstractOp()
.
- return
result
.
上面流程的?
,就代表AbstractOp()
可能会报错。一旦报错,就返回错误,否则取出值。
除了?
,ES 6 规格还使用另一个简写符号!
。
- Let
result
be! AbstractOp()
.
- return
result
.
上面流程的!
,代表AbstractOp()
不会报错,返回的一定是 normal completion,总是可以取出值。
相等运算符
下面通过一些例子,介绍如何使用这份规格。
相等运算符(==
)是一个很让人头痛的运算符,它的语法行为多变,不符合直觉。这个小节就看看规格怎么规定它的行为。
请看下面这个表达式,请问它的值是多少。
0 == null
如果你不确定答案,或者想知道语言内部怎么处理,就可以去查看规格,7.2.12 小节是对相等运算符(==
)的描述。
规格对每一种语法行为的描述,都分成两部分:先是总体的行为描述,然后是实现的算法细节。相等运算符的总体描述,只有一句话。
“The comparison
x == y
, wherex
andy
are values, producestrue
orfalse
.”
上面这句话的意思是,相等运算符用于比较两个值,返回true
或false
。
下面是算法细节。
- ReturnIfAbrupt(x).
- ReturnIfAbrupt(y).
- If
Type(x)
is the same as `Type(y)` , then 1. Return the result of performing Strict Equality Comparison `x === y`.
- If
x
isnull
andy
isundefined
, returntrue
.
- If
x
isundefined
andy
isnull
, returntrue
.
- If
Type(x)
is Number andType(y)
is String, return the result of the comparisonx == ToNumber(y)
.
- If
Type(x)
is String andType(y)
is Number, return the result of the comparisonToNumber(x) == y
.
- If
Type(x)
is Boolean, return the result of the comparisonToNumber(x) == y
.
- If
Type(y)
is Boolean, return the result of the comparisonx == ToNumber(y)
.
- If
Type(x)
is either String, Number, or Symbol andType(y)
is Object, then return the result of the comparisonx == ToPrimitive(y)
.
- If
Type(x)
is Object andType(y)
is either String, Number, or Symbol, then return the result of the comparisonToPrimitive(x) == y
.
- Return
false
.
上面这段算法,一共有 12 步,翻译如下。
- 如果
x
不是正常值(比如抛出一个错误),中断执行。
- 如果
y
不是正常值,中断执行。
- 如果
Type(x)
与Type(y)
相同,执行严格相等运算x === y
。
- 如果
x
是null
,y
是undefined
,返回true
。
- 如果
x
是undefined
,y
是null
,返回true
。
- 如果
Type(x)
是数值,Type(y)
是字符串,返回x == ToNumber(y)
的结果。
- 如果
Type(x)
是字符串,Type(y)
是数值,返回ToNumber(x) == y
的结果。
- 如果
Type(x)
是布尔值,返回ToNumber(x) == y
的结果。
- 如果
Type(y)
是布尔值,返回x == ToNumber(y)
的结果。
- 如果
Type(x)
是字符串或数值或Symbol
值,Type(y)
是对象,返回x == ToPrimitive(y)
的结果。
- 如果
Type(x)
是对象,Type(y)
是字符串或数值或Symbol
值,返回ToPrimitive(x) == y
的结果。
- 返回
false
。
由于0
的类型是数值,null
的类型是 Null(这是规格4.3.13 小节的规定,是内部 Type 运算的结果,跟typeof
运算符无关)。因此上面的前 11 步都得不到结果,要到第 12 步才能得到false
。
0 == null // false