Iterator 和 for...of 循环
Iterator 和 for...of 循环
  Iterator（遍历器）的概念
    JavaScript 原有的表示“集合”的数据结构，主要是数组（Array）和对象（Object），ES6 又添加了Map和Set
    用户还可以组合使用它们，定义自己的数据结构，比如数组的成员是Map，Map的成员是对象
    这样就需要一种统一的接口机制，来处理所有不同的数据结构
    遍历器（Iterator）就是这样一种机制
    它是一种接口，为各种不同的数据结构提供统一的访问机制。
    任何数据结构只要部署 Iterator 接口，就可以完成遍历操作
    Iterator 的作用有三个
      一是为各种数据结构，提供一个统一的、简便的访问接口
      二是使得数据结构的成员能够按某种次序排列
      三是 ES6 创造了一种新的遍历命令for...of循环，Iterator 接口主要供for...of消费
    Iterator 的遍历过程是这样的
      （1）创建一个指针对象，指向当前数据结构的起始位置。也就是说，遍历器对象本质上，就是一个指针对象。
      （2）第一次调用指针对象的next方法，可以将指针指向数据结构的第一个成员。
      （3）第二次调用指针对象的next方法，指针就指向数据结构的第二个成员。
      （4）不断调用指针对象的next方法，直到它指向数据结构的结束位置。
      每一次调用next方法，都会返回数据结构的当前成员的信息。具体来说，就是返回一个包含value和done两个属性的对象。其中，value属性是当前成员的值，done属性是一个布尔值，表示遍历是否结束
  默认 Iterator 接口
    Iterator 接口的目的，就是为所有数据结构，提供了一种统一的访问机制，即for...of循环。当使用for...of循环遍历某种数据结构时，该循环会自动去寻找 Iterator 接口
    一种数据结构只要部署了 Iterator 接口，我们就称这种数据结构是“可遍历的”（iterable）
    ES6 规定，默认的 Iterator 接口部署在数据结构的Symbol.iterator属性，或者说，一个数据结构只要具有Symbol.iterator属性，就可以认为是“可遍历的”（iterable）
    Symbol.iterator属性本身是一个函数，就是当前数据结构默认的遍历器生成函数。执行这个函数，就会返回一个遍历器
    ES6 的有些数据结构原生具备 Iterator 接口，原因在于，这些数据结构原生部署了Symbol.iterator属性，比如
      Array
      Map
      Set
      String
      TypedArray
      函数的 arguments 对象
      NodeList 对象
    对于原生部署 Iterator 接口的数据结构，不用自己写遍历器生成函数，for...of循环会自动遍历它们。除此之外，其他数据结构（主要是对象）的 Iterator 接口，都需要自己在Symbol.iterator属性上面部署，这样才会被for...of循环遍历
    对象（Object）之所以没有默认部署 Iterator 接口，是因为对象的哪个属性先遍历，哪个属性后遍历是不确定的，需要开发者手动指定。本质上，遍历器是一种线性处理，对于任何非线性的数据结构，部署遍历器接口，就等于部署一种线性转换。不过，严格地说，对象部署遍历器接口并不是很必要，因为这时对象实际上被当作 Map 结构使用，ES5 没有 Map 结构，而 ES6 原生提供了。
    注意，普通对象部署数组的Symbol.iterator方法，并无效果
    如果Symbol.iterator方法对应的不是遍历器生成函数（即会返回一个遍历器对象），解释引擎将会报错
  调用 Iterator 接口的场合
    （1）解构赋值
      对数组和 Set 结构进行解构赋值时，会默认调用Symbol.iterator方法
    （2）扩展运算符
    （3）yield*
      yield*后面跟的是一个可遍历的结构，它会调用该结构的遍历器接口
    （4）由于数组的遍历会调用遍历器接口，所以任何接受数组作为参数的场合，其实都调用了遍历器接口
  字符串的 Iterator 接口
    字符串是一个类似数组的对象，也原生具有 Iterator 接口
  Symbol.iterator()方法的最简单实现
    使用 Generator 函数
  遍历器对象的 return()，throw()
    next()方法是必须部署
    return()和throw()都是可选
    return()方法的使用场合是，如果for...of循环提前退出（通常是因为出错，或者有break语句），就会调用return()方法。如果一个对象在完成遍历前，需要清理或释放资源，就可以部署return()方法
    注意，return()方法必须返回一个对象，这是 Generator 语法决定的
    throw()方法主要是配合 Generator 函数使用
  for...of 循环
    ES6引入了for...of循环，作为遍历所有数据结构的统一的方法
    数组
      数组原生具备iterator接口（即默认部署了Symbol.iterator属性）
    Set 和 Map 结构
    计算生成的数据结构
      有些数据结构是在现有数据结构的基础上，计算生成的。比如，ES6 的数组、Set、Map 都部署了以下三个方法，调用后都返回遍历器对象
        entries() 返回一个遍历器对象，用来遍历[键名, 键值]组成的数组。对于数组，键名就是索引值；对于 Set，键名与键值相同。Map 结构的 Iterator 接口，默认就是调用entries方法。
        keys() 返回一个遍历器对象，用来遍历所有的键名。
        values() 返回一个遍历器对象，用来遍历所有的键值。
    类似数组的对象
      字符串
      DOM NodeList
      arguments
    对象
      普通的对象，for...of结构不能直接使用，会报错，必须部署了 Iterator 接口后才能使用
    与其他遍历语法的比较
      最原始的for循环
        写法麻烦
      forEach
        这种写法的问题在于，无法中途跳出forEach循环，break命令或return命令都不能奏效
      for...in
        缺点是
          数组的键名是数字，但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
          for...in循环不仅遍历数字键名，还会遍历手动添加的其他键，甚至包括原型链上的键。
          某些情况下，for...in循环会以任意顺序遍历键名
          总之，for...in循环主要是为遍历对象而设计的，不适用于遍历数组
      for...of循环相比上面几种做法，有一些显著的优点
        有着同for...in一样的简洁语法，但是没有for...in那些缺点。
        不同于forEach方法，它可以与break、continue和return配合使用。
        提供了遍历所有数据结构的统一操作接口。
  const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};