读懂规格
读懂规格
  概述
    规格文件是计算机语言的官方标准，详细描述语法规则和实现方法
    一般来说，没有必要阅读规格，除非你要写编译器
    但是，如果你遇到疑难的语法问题，实在找不到答案，这时可以去查看规格文件，了解语言标准是怎么说的。规格是解决问题的“最后一招”
    本章介绍如何读懂 ECMAScript 6 的规格文件
    ECMAScript 6 的规格，可以在 ECMA 国际标准组织的官方网站（www.ecma-international.org/ecma-262/6.0/）免费下载和在线阅读
  术语
    抽象操作
      所谓“抽象操作”（abstract operations）就是引擎的一些内部方法，外部不能调用
    Record 和 field
      ES6 规格将键值对（key-value map）的数据结构称为 Record，其中的每一组键值对称为 field。这就是说，一个 Record 由多个 field 组成，而每个 field 都包含一个键名（key）和一个键值（value）
    [[Notation]]
      ES6 规格大量使用[[Notation]]这种书写法，比如[[Value]]、[[Writable]]、[[Get]]、[[Set]]等等。它用来指代 field 的键名
    Completion Record
      每一个语句都会返回一个 Completion Record，表示运行结果。每个 Completion Record 有一个[[Type]]属性，表示运行结果的类型。[[Type]]属性有五种可能的值
        normal
        return
        throw
        break
        continue
  抽象操作的标准流程
    一般是这样
      Let result be AbstractOp().
      If result is an abrupt completion, return result.
      Set result to result.[[Value]].
      return result.
    ES6 规格将这个标准流程，使用简写的方式表达
      Let result be AbstractOp().
      ReturnIfAbrupt(result).
      return result.
  相等运算符
    规格7.2.12 小节是对相等运算符（==）的描述
    规格对每一种语法行为的描述，都分成两部分：先是总体的行为描述，然后是实现的算法细节
    相等运算符的总体描述，只有一句话：“The comparison x == y, where x and y are values, produces true or false.”，意思是相等运算符用于比较两个值
    里面会由具体的算法细节
  数组的空位
    规格的12.2.5 小节《数组的初始化》给出了答案
    “Array elements may be elided at the beginning, middle or end of the element list. Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined. If an element is elided at the end of an array, that element does not contribute to the length of the Array.”
    即："数组成员可以省略。只要逗号前面没有任何表达式，数组的length属性就会加 1，并且相应增加其后成员的位置索引。被省略的成员不会被定义。如果被省略的成员是数组最后一个成员，则不会导致数组length属性增加。”
    这就解释了为什么in运算符、数组的hasOwnProperty方法、Object.keys方法，都取不到空位的属性名。因为这个属性名根本就不存在，规格里面没说要为空位分配属性名(位置索引），只说要为下一个元素的位置索引加 1
  数组的 map 方法
    规格的22.1.3.15 小节定义了数组的map方法
    算法描述太长，此处省略
    V8 引擎对map方法的实现如下，可以看到跟规格的算法描述完全一致
  function ArrayMap(f, receiver) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayMap(f, receiver, array, length);
}
function InnerArrayMap(f, receiver, array, length) {
if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
var accumulator = new InternalArray(length);
var is_array = IS_ARRAY(array);
var stepping = DEBUG_IS_STEPPING(f);
for (var i = 0; i < length; i++) {
if (HAS_INDEX(array, i, is_array)) {
var element = array[i];
// Prepare break slots for debugger step in.
if (stepping) %DebugPrepareStepInIfStepping(f);
accumulator[i] = %_Call(f, receiver, element, i, array);
}
}
var result = new GlobalArray();
%MoveArrayContents(accumulator, result);
return result;
}