11 Iterator 和 for...of 循环

83 阅读5分钟
├── Iteratorfor...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 接口。
│   │     └─ (3yield* `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`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性
│   │     └─ SetMap 结构
│   │         └─ SetMap 结构也原生具有 Iterator 接口,可以直接使用`for...of`循环。
│   │         └─ 值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
│   │     └─ 计算生成的数据结构
│   │         └─ 有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、SetMap 都部署了以下三个方法,调用后都返回遍历器对象。
│   │         └─ - `entries()` 返回一个遍历器对象,用来遍历`[键名, 键值]`组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用`entries`方法。
│   │         └─ - `keys()` 返回一个遍历器对象,用来遍历所有的键名。
│   │         └─ - `values()` 返回一个遍历器对象,用来遍历所有的键
│   │     └─ 类似数组的对象
│   │         └─ 类似数组的对象包括好几类。下面是`for...of`循环用于字符串、DOM NodeList 对象、`arguments`对象的例子。
│   │         └─ 对于字符串来说,`for...of`循环还有一个特点,就是会正确识别 32UTF-16 字符。
│   │         └─ 并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用`Array.from`方法将其转为数组
│   │     └─ 对象
│   │         └─ 对于普通的对象,`for...of`结构不能直接使用,会报错,对于普通的对象,`for...in`循环可以遍历键名,`for...of`循环会报错。
│   │         └─ 一种解决方法是,使用`Object.keys`方法将对象的键名生成一个数组,然后遍历这个数组。 另一个方法是使用 Generator 函数将对象重新包装一下。
│   │     └─ 与其他遍历语法的比较
│   │         └─ - 有着同`for...in`一样的简洁语法,但是没有`for...in`那些缺点。
│   │         └─ - 不同于`forEach`方法,它可以与`break``continue``return`配合使用。
│   │         └─ - 提供了遍历所有数据结构的统一操作接口。