├── Iterator 和 for...of 循环
│ ├── Iterator(遍历器)的概念
│ │ └─ 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
│ │ └─ Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令`for...of`循环,Iterator 接口主要供`for...of`消费。
│ │ └─ Iterator 的遍历过程
│ │ └─ (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
│ │ └─ (2)第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员。
│ │ └─ (3)第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员
│ │ └─ (4)不断调用指针对象的`next`方法,直到它指向数据结构的结束位置。
│ ├── 默认 Iterator 接口
│ │ └─
│ │ └─ ES6 规定,默认的 Iterator 接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。
│ │ └─ 普通对象部署数组的`Symbol.iterator`方法,并无效果。
│ │ └─ 如果`Symbol.iterator`方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
│ │ └─ 有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。
│ ├── 默认调用 Iterator 接口的场合
│ │ └─ (1)解构赋值 对数组和 Set 结构进行解构赋值时,会默认调用`Symbol.iterator`方法。
│ │ └─ (2)扩展运算符 扩展运算符(...)也会调用默认的 Iterator 接口。
│ │ └─ (3)yield* `yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
│ │ └─ (4)其他场合 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
│ │ └─ - for...of
│ │ └─ - Array.from()
│ │ └─ - Map(), Set(), WeakMap(), WeakSet()(比如`new Map([['a',1],['b',2]])`)
│ │ └─ - Promise.all()
│ │ └─ - Promise.race()
│ ├── 字符串的 Iterator 接口
│ │ └─ 字符串是一个类似数组的对象,也原生具有 Iterator 接口。
│ │ └─ 调用`Symbol.iterator`方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历。可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。
│ ├── Iterator 接口与 Generator 函数
│ │ └─ [Symbol.iterator]: function* () {}
│ │ └─ * [Symbol.iterator]() {}
│ │ └─ `Symbol.iterator()`方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。
│ ├── 遍历器对象的 return(),throw()
│ │ └─ 遍历器对象除了具有`next()`方法,还可以具有`return()`方法和`throw()`方法。如果你自己写遍历器对象生成函数,那么`next()`方法是必须部署的,`return()`方法和`throw()`方法是否部署是可选的。
│ │ └─ `return()`方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有`break`语句),就会调用`return()`方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署`return()`方法。
│ │ └─ `return()`方法必须返回一个对象,这是 Generator 语法决定的。
│ ├── for...of 循环
│ │ └─ 一个数据结构只要部署了`Symbol.iterator`属性,就被视为具有 iterator 接口,就可以用`for...of`循环遍历它的成员。也就是说,`for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。
│ │ └─ 数组
│ │ └─ 数组原生具备`iterator`接口(即默认部署了`Symbol.iterator`属性),`for...of`循环本质上就是调用这个接口产生的遍历器,
│ │ └─ `for...of`循环可以代替数组实例的`forEach`方法。
│ │ └─ `for...in`循环读取键名,`for...of`循环读取键值。
│ │ └─ `for...of`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性
│ │ └─ Set 和 Map 结构
│ │ └─ Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用`for...of`循环。
│ │ └─ 值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
│ │ └─ 计算生成的数据结构
│ │ └─ 有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
│ │ └─ - `entries()` 返回一个遍历器对象,用来遍历`[键名, 键值]`组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用`entries`方法。
│ │ └─ - `keys()` 返回一个遍历器对象,用来遍历所有的键名。
│ │ └─ - `values()` 返回一个遍历器对象,用来遍历所有的键
│ │ └─ 类似数组的对象
│ │ └─ 类似数组的对象包括好几类。下面是`for...of`循环用于字符串、DOM NodeList 对象、`arguments`对象的例子。
│ │ └─ 对于字符串来说,`for...of`循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。
│ │ └─ 并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用`Array.from`方法将其转为数组
│ │ └─ 对象
│ │ └─ 对于普通的对象,`for...of`结构不能直接使用,会报错,对于普通的对象,`for...in`循环可以遍历键名,`for...of`循环会报错。
│ │ └─ 一种解决方法是,使用`Object.keys`方法将对象的键名生成一个数组,然后遍历这个数组。 另一个方法是使用 Generator 函数将对象重新包装一下。
│ │ └─ 与其他遍历语法的比较
│ │ └─ - 有着同`for...in`一样的简洁语法,但是没有`for...in`那些缺点。
│ │ └─ - 不同于`forEach`方法,它可以与`break`、`continue`和`return`配合使用。
│ │ └─ - 提供了遍历所有数据结构的统一操作接口。