15

前言

本文是对《ECMAScript 2018 Language Specification》的解读。
本文是对标准的概述性导读,不是对 ES2018特性的详细描述,也不会针对某个技术点进行详细展开,但是会附上相关文章外链。

规格介绍

整个文档有引言+27个章节+7篇附录,大概五六百页的样子。

引言和前面3章部分,都是在讲规格本身,跟JS语言本身无关。内容很少,可以快速过一遍。

  • Introduction 部分 介绍了语言历史和标准化历程;
  • 前3章 ScopeConformanceNormative References 主要介绍了文档的范围、一致性和参考文献。所谓一致性,实际上是标准实现的一致性,任何实现ECMAScript 标准的语言,都必须完全实现文档中描述的语法和语义,并且可以有规格之外的自定义程序语法。

语言概述

第4章 Overview 是对语言的整体介绍。涵盖了Web脚本语言环境、ES基本概念和专业术语,以及严格模式的简单介绍。这里跟大家分享几个有意思的点。

奇葩的面向对象机制

ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.
ES是一门面向对象的语言,这是官方描述!(这有什么奇怪的啊,大家都知道啊)但是ES的面向对象设计机制却是与众不同,大有学问(这有什么啊,不就原型链嘛)。我们多少都了解一些,但要完全讲清楚,恐怕专门开一篇博客也不够。

但我还是尝试专门写了一篇:《如何优雅的解读JS的面向对象机制》

脚本语言的逆袭

ECMAScript was originally designed to be used as a scripting language, but has become widely used as a general-purpose programming language.

这个就有点屌了,ES最初是被拿来当Web脚本语言用的,但现在已经成了时下最流行的通用编程语言之一。此中缘由大家应该也很清楚,不多说,只是抒发一下感慨:Always bet on JS可不是乱说的。

有关对象的描述

本章还列举出了JS中的专业名词及解释,比如类型、原始值、对象、构造器、原型......等概念。有意思的是标准中关于对象的描述在ES5里面有三种:

  • native object(原生对象),指语义完全由规范定义并且不掺杂任何宿主环境定义的的对象;
  • build-in object(内置对象),由ECMA实现提供,程序执行时就存在的对象。所有内置对象都是原生对象。
  • host object(宿主对象),由执行环境提供,比如浏览器的window对象和history对象。JS里的对象不是原生对象就是宿主对象。

但是在ES6之后就改成了四种:

  • ordinary object:普通对象,只要具备了对象的所有基本内置方法就可以了。
  • exotic object:外来对象,如果不具备标准对象所有的基本内置方法,就是外来对象。JS里的对象不是普通对象就是外来对象。
  • standard object:标准对象,语义由本规范定义的对象。
  • built-in object:内置对象,跟ES5中描述一样。

对比来看,前者是以宿主环境为划分条件,后者则是以对象的基本内置方法。ES6之后其实划分的更细了。

记法约定

第5章 Notational Conventions 详细介绍了规范描述中用到的一些句法、词法以及算法约定等内容,如果要看懂后面的有关语法行为,函数实现的详细描述,就得看懂这章,看完之后你甚至可以照着标准实现一遍。

这章涉及大量编译基本知识,还是强烈建议花些时间看下,不然后面可能没法继续。你需要知道以下概念:

上下文无关文法

作为ECMAScript规格文档,自然需要用一种专业的方式来描述这门语言,这种专业的描述语言的方法,就是所谓的文法(文法由若干产生式组成)。而上下文无关的意思,就是所有产生式的左边只有一个非终结符,因为只有这样,产生式右边的串才能规约到左边的非终结符,否则就是上下文相关。大部分编程语言都是上下文无关文法,ECMAScript也不例外。

词法、正则文法、数字字符串文法和句法约定

一个冒号“:”作为分隔符分割句法的产生式。两个冒号“::”作为分隔符分割词法和正则的文法产生式。词法和正则的文法共享某些产生式。三个冒号“:::”作为分隔符分割数字字符串文法的产生式。然后列举了各种句法,文法标记,总之很多概念,此处不展开。

内部机制

第6到8章详细描述了语言运行的内部机制,从宏观上对ES进行描述,包括数据类型和值,语言内部的抽象操作,以及代码执行的上下文相关知识。

类型

ES中的类型可细分为ES语言类型和规范类型,语言类型对应的是程序中直接被操作的值的类型,包括Undefined,Null,Boolean,Number,String,Object,Symbol。理解类型,是理解这门语言的基础。

首先是Undefind和Null,二者区别可参考 undefined与null的区别 - 阮一峰。在一门编程语言中对于“空”的描述用到了两种基本类型,估计只有JavaScript了。其实一开始只有null,后来为了解决类型转换和错误处理问题引入了undefined。

undefined 表示此处应该有个值,但是这个值还没给出来,其实就是占了个坑,这个坑是语言内部实现帮你做的,你不用管。null 才是真正意义上的空值,表示对象世界中的“无”。正所谓道生一,一生二,二生三,三生万物。JS中万物皆对象,所有对象的原型链都可以上溯到唯一的Object,而Object的原型,正是万物之始源,混沌之道null。所以JS中null的意义远超其他编程语言,这正是让JS的面向对象思想与道家哲学完美契合的重要一笔。

所以个人理解,Undefined虽然作为基本类型,解决的却是语言内部处理问题,所以永远不要在代码中主动出现,要在语义上处理空就用null。所有因为undefined带来的问题,基本上是占着茅坑不拉屎的行为导致。所以google在Dart中就只有null,而没有undefined,因为undefined解决的问题完全可以在语言内部解决,没必要暴露给用户。

Boolean和Symbol没啥好说的,数值的设计也是从简,只有一个Number类型。有意思的是String,官方对于String类型的描述:

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 2e53 - 1 elements.
翻译过来就是指所有有限的零个或多个16位无符号整数值的有序序列(共计2e53 - 1个元素)。这个2e53 - 1是怎么来的呢,按照16位无符号整数值计算的话?

更有意思的是,String中的每一个字符都被视为独立的UTF-16代码单元,即占2个字节,作用在字符串上的所有操作都视它们为无差别的16位无符号整数(这里的UTF-16,其实是指内部实现,计算机内存中都是基于unicode编码的,只是在存储或读取时会进行UTF-8或者其他编码类型转换)。但是UTF-16却有两种长度的字符,U+0000到U+FFFF之间的字符占2个字节,U+10000到U+10FFFF之间的字符占4字节。对于4字节的字符ES是无法准确处理的,需要自己去根据编码值情况判断,这也是一大坑爹之处。

对此,我也专门写了一篇:《深入理解JavaScript中的String类型-未发布》

除了以上语言类型,整个规范中还有用于描述这门语言的规范类型,规范类型的值是规范自己造的,有的还是ES表达式计算的中间结果,所以没必要对应到特定的语言类型上。若非特别说明,ES中的类型通常指语言类型。

操作摘要

类型之间会涉及到各种运算,这就会涉及到各种操作运算。比如类型转换涉及到的内部机制和算法流程,7.1 Type Conversion 都有详细说明。7.2 Testing and Comparison Operations 讲了测试和比较操作,比如测试一个对象是否是数组,是否数字,是否构造函数,以及 ===== 的定义等等。以数组测试操作isArray(argument)为例,标准中的描述如下:

1. If Type(argument) is not Object, return false.
2. If argument is an Array exotic object, return true.
3. If argument is a Proxy exotic object, then
    a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
    b. Let target be argument.[[ProxyTarget]].
    c. Return ? IsArray(target).
4. Return false.

相对于ES5来说,规范中增加了对Proxy的处理。我们再看 underscope v1.8.3 源码中对isArray的实现:

_.isArray = Array.isArray || function(obj) {
    return toString.call(obj) === '[object Array]';
}

相对于ES5来说,规范中增加了对Proxy的处理,目前来看这是引擎内部处理的,对现在很多检测数组的方法并不影响。

语言实现细节

这是个大坑,还是附上ES2018正式版规范地址吧:

https://www.ecma-internationa...

如果想快速了解一下新特性,可以看这里:

https://medium.com/front-end-...

具体有哪些 finished proposals 可以参考 tc39 的 GitHub

如何优雅的阅读ECMA标准

由于目前使用最为广泛的还是ECMA 5.1版本,所以在阅读ES2018之前,建议先把5.1的标准看一遍,方便对比。好在W3C中文站有5.1的100%翻译版本:

ES5中文版: https://www.w3.org/html/ig/zh...

然后可以再看ES6也就是ES2015的标准,虽然没有中文版,不过可以参考阮老师的ES6入门,顺便也可以瞄一眼ES2016的标准:

ES 2015: http://www.ecma-international...
ECMAScript 6入门教程: http://es6.ruanyifeng.com/
ES 2016: http://www.ecma-international...

期间有任何疑惑可以参考MDN上的JS参考文档,非常全面,涵盖了从入门到精通。

https://developer.mozilla.org...

看完这些再看ES2018就会非常轻松了:

https://tc39.github.io/ecma262/

动感小前端
1.5k 声望272 粉丝

动感小前端,动次打次