一、概述规范文件(specification)是计算机语言的官方标准,详细描述了语法规则和实现方法。通常,除非您正在编写编译器,否则无需阅读规范。由于规范非常抽象和精炼,且缺少示例,不易理解,对解决实际应用问题帮助不大。但是,如果你遇到困难的语法题,实在找不到答案,那你可以去规范文件中了解一下语言标准是怎么说的。规范是解决问题的“最后手段”。这对于JavaScript语言是必需的。由于其使用场景复杂,语法规则不统一,异常情况多,各种运行环境的行为也不一致,导致千奇百怪的语法问题层出不穷。任何一本语法书都不可能涵盖所有情况。查看规范是解决语法问题的最可靠和最权威的***方法之一。本文介绍如何阅读ECMAScript6规范文档。ECMAScript6的规范可以在ECMA国际标准组织官网(www.ecma-international.org/ecma-262/6.0/)免费下载和在线阅读。这份规范文件相当庞大,一共26章,如果打印成A4纸,足足有545页。它的特点是非常详细,每一个语法行为,每一个函数的实现都描述的很详细,很清楚。基本上,编译器编写者只需要将每个步骤翻译成代码即可。这在很大程度上确保了所有ES6实现的行为一致。在ECMAScript6规范的26章中,第1章到第3章是文档本身的介绍,与语言关系不大。第4章是语言总体设计的描述,有兴趣的读者可以阅读。第5章到第8章描述了宏观层面的语言。第5章介绍规范的名词解释和编写,第6章介绍数据类型,第7章介绍语言中使用的抽象操作,第8章介绍代码如何运行。第9章对第26章介绍具体语法。对于一般用户,除第4章外,其他章节均涉及到某一方面的细节,无需通读,需要时查阅相关章节即可。以下是如何使用此规范的一些示例。2.相等运算符首先看这个例子,下面的表达式的值是多少。0==null如果你不确定答案,或者想知道语言内部是如何处理的,你可以查看规范,7.2.12节是对相等运算符(==)的描述。规范中对每个语法行为的描述分为两部分:首先是整体行为描述,然后是实现算法细节。对相等运算符的笼统描述,只有一句话。“比较x==y,其中x和y是值,产生真或假。”上面这句话的意思是等于运算符用来比较两个值,返回true或false。算法细节如下。ReturnIfAbrupt(x)。ReturnIfAbrupt(y)。如果Type(x)与Type(y)相同,则返回严格相等比较的结果x===y。如果xisnull和yiisundefined,则返回true。andType(y)为String,返回比较结果x==ToNumber(y)。IfType(x)isStringandType(y)isNumber,返回comparisonToNumber(x)==y的结果。如果Type(x)是Boolean,则返回comparisonToNumber(x)==y的结果。如果Type(y)是Boolean,则返回比较的结果x==ToNumber(y)。如果Type(x)是String、Number或Symbol且Type(y)是Object,则返回比较结果x==ToPrimitive(y)。如果Type(x)是Object并且Type(y)是String、Number或Symbol,则返回comparisonToPrimitive(x)==y的结果。返回假。上述算法一共12步,译文如下。如果x不是正常值(例如抛出错误),则执行中断。如果y不正常,则执行中断。如果Type(x)与Type(y)相同,则执行严格相等操作x===y。如果x为null且y未定义,则返回true。如果x未定义且y为空,则返回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的结果。返回假。由于0的类型是一个值,所以null的类型为Null(这是4.3.13节中的规范,是内部Type运算的结果,与typeof运算符无关)。所以上面前11步得不到结果,需要第12步才能得到false。0==null//false三、数组的空位我们再看一个例子。consta1=[undefined,undefined,undefined];consta2=[,,,];a1.length//3a2.length//3a1[0]//undefineda2[0]//undefineda1[0]===a2[0]//true上面代码中,数组a1的成员是三个undefined,数组a2的成员是三个空位。这两个数组很相似,长度都是3,每个位置的成员都读为undefined。然而,它们实际上有很大的不同。a1中的0//a2中的true0//falsea1.hasOwnProperty(0)//truea2.hasOwnProperty(0)//falseObject.keys(a1)//["0","1","2"]Object.keys(a2)//[]a1.map(n=>1)//[1,1,1]a2.map(n=>1)//[,,,]上面的代码一共列出了四个操作,数组a1和a2的结果不同。前三个操作(in操作符、数组的hasOwnProperty方法、Object.keys方法)都表示无法从数组a2中获取到属性名。***一个操作(数组的map方法)显示数组a2没有遍历过。为什么a1和a2成员的行为不一致?数组成员未定义或为空,有什么区别?规范《数组的初始化》的12.2.5节给出了答案。“数组元素可以在元素列表的开头、中间或结尾省略。只要元素列表中的逗号前面没有AssignmentExpression(即开头或另一个逗号之后的逗号),则缺少的数组元素会产生影响到数组的长度并增加后续元素的索引。未定义删除的数组元素。如果在数组末尾删除了一个元素,则该元素不会影响数组的长度。”译文如下。"数组成员可以省略,只要逗号前没有表达式,数组的length属性就会加1,后面的成员的位置索引也会相应增加,省略的成员不会被定义。如果省略的成员是数组***成员,不会导致数组的length属性增加。”上面的规范明确了数组的空缺会体现在length属性中,也就是说空缺是有自己的位置的,但是这个位置的值是未定义的。也就是说,这个值不存在。如果一定要读取,则结果是undefined(因为undefined意味着在JavaScript语言中不存在)。这就解释了为什么in操作符,数组的hasOwnProperty方法,Object.keys方法都取不到空位的属性名。因为这个属性名根本不存在,所以规范中并没有说要把属性名(位置索引)赋给空的空间,只是说下一个元素的位置索引要加1。至于为什么数组的map方法会跳过空格,请看下一节。4.数组映射方法规范的22.1.3.15节定义了数组映射方法。本节首先描述map方法的一般行为,并没有提及数组空位。下面的算法描述如下。LetObeToObject(这个值)。ReturnIfAbrupt(O)。LetlenbeToLength(获取(O,“长度”))。ReturnIfAbrupt(len)。IfIsCallable(callbackfn)isfalse,抛出TypeError异常。如果提供了这个参数,就让这个参数;否则让我们未定义。LetAbeArraySpeciesCreate(O,len)。ReturnIfAbrupt(A)。Letkbe0.重复,whilek
