读懂 ECMAScript 规格

概述

规格文件是计算机语言的官方标准,详细描述语法规则和实现方法。

一般来说,没有必要阅读规格,除非你要写编译器。因为规格写得非常抽象和精炼,又缺乏实例,不容易理解,而且对于解决实际的应用问题,帮助不大。但是,如果你遇到疑难的语法问题,实在找不到答案,这时可以去查看规格文件,了解语言标准是怎么说的。规格是解决问题的“最后一招”。

这对 JavaScript 语言很有必要。因为它的使用场景复杂,语法规则不统一,例外很多,各种运行环境的行为不一致,导致奇怪的语法问题层出不穷,任何语法书都不可能囊括所有情况。查看规格,不失为一种解决语法问题的最可靠、最权威的终极方法。

本章介绍如何读懂 ECMAScript 6 的规格文件。

ECMAScript 6 的规格,可以在 ECMA 国际标准组织的官方网站(www.ecma-international.org/ecma-262/6.0/)免费下载和在线阅读。

术语

ES6 规格使用了一些专门的术语,了解这些术语,可以帮助你读懂规格。本节介绍其中的几个。

抽象操作

所谓“抽象操作”(abstract operations)就是引擎的一些内部方法,外部不能调用。规格定义了一系列的抽象操作,规定了它们的行为,留给各种引擎自己去实现。

举例来说,Boolean(value)的算法,第一步是这样的。

  1. Let b be ToBoolean(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的情况,即运行出错;breakcontinuereturn这三个值都只出现在特定场景,可以不用考虑。

抽象操作的标准流程

抽象操作的运行流程,一般是下面这样。

  1. Let result be AbstractOp().
  1. If result is an abrupt completion, return result.
  1. Set result to result.[[Value]].
  1. return result.

上面的第一步调用了抽象操作AbstractOp(),得到result,这是一个 Completion Record。第二步,如果result属于 abrupt completion,就直接返回。如果此处没有返回,表示result属于 normal completion。第三步,将result的值设置为resultCompletionRecord.[[Value]]。第四步,返回result

ES6 规格将这个标准流程,使用简写的方式表达。

  1. Let result be AbstractOp().
  1. ReturnIfAbrupt(result).
  1. return result.

这个简写方式里面的ReturnIfAbrupt(result),就代表了上面的第二步和第三步,即如果有报错,就返回错误,否则取出值。

甚至还有进一步的简写格式。

  1. Let result be ? AbstractOp().
  1. return result.

上面流程的?,就代表AbstractOp()可能会报错。一旦报错,就返回错误,否则取出值。

除了?,ES 6 规格还使用另一个简写符号!

  1. Let result be ! AbstractOp().
  1. return result.

上面流程的!,代表AbstractOp()不会报错,返回的一定是 normal completion,总是可以取出值。

相等运算符

下面通过一些例子,介绍如何使用这份规格。

相等运算符(==)是一个很让人头痛的运算符,它的语法行为多变,不符合直觉。这个小节就看看规格怎么规定它的行为。

请看下面这个表达式,请问它的值是多少。

0 == null

如果你不确定答案,或者想知道语言内部怎么处理,就可以去查看规格,7.2.12 小节是对相等运算符(==)的描述。

规格对每一种语法行为的描述,都分成两部分:先是总体的行为描述,然后是实现的算法细节。相等运算符的总体描述,只有一句话。

“The comparison x == y, where x and y are values, produces true or false.”

上面这句话的意思是,相等运算符用于比较两个值,返回truefalse

下面是算法细节。

  1. ReturnIfAbrupt(x).
  1. ReturnIfAbrupt(y).
  1. If

Type(x)

is the same as



`Type(y)`



, then


1.  Return the result of performing Strict Equality Comparison `x === y`.
  1. If x is null and y is undefined, return true.
  1. If x is undefined and y is null, return true.
  1. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  1. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  1. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  1. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  1. If Type(x) is either String, Number, or Symbol and Type(y) is Object, then return the result of the comparison x == ToPrimitive(y).
  1. If Type(x) is Object and Type(y) is either String, Number, or Symbol, then return the result of the comparison ToPrimitive(x) == y.
  1. Return false.

上面这段算法,一共有 12 步,翻译如下。

  1. 如果x不是正常值(比如抛出一个错误),中断执行。
  1. 如果y不是正常值,中断执行。
  1. 如果Type(x)Type(y)相同,执行严格相等运算x === y
  1. 如果xnullyundefined,返回true
  1. 如果xundefinedynull,返回true
  1. 如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
  1. 如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
  1. 如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
  1. 如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
  1. 如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
  1. 如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
  1. 返回false

由于0的类型是数值,null的类型是 Null(这是规格4.3.13 小节的规定,是内部 Type 运算的结果,跟typeof运算符无关)。因此上面的前 11 步都得不到结果,要到第 12 步才能得到false

0 == null // false
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容