:::danger 通过递增索引来遍历有序集合的缺点是什么?
- 迭代之前需要事先知道如何使用数据结构
- 遍历顺序并不是数据结构固有的。 :::
迭代器模式
:::danger 可迭代对象有哪些特点? :::
迭 代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代 其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。
可迭代协议
实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现 Iterator 接口的对象的能力。在 ECMAScript 中,这意味着必须暴露一个属性作为“默认迭代器”,而 且这个属性必须使用特殊的 Symbol.iterator 作为键。
var arr = [];arr.__proto__[Symbol.iterator] // ƒ values() { [native code] }
很多内置类型都实现了 Iterable 接口:
- String
- Number
- Map
- Set
- arguments
- NodeList
可以接收迭代器的原生对象有:
- for-of 循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由期约组成的可迭代对象
- Promise.race()接收由期约组成的可迭代对象
- yield*操作符,在生成器中使用
可选的 return()方法用于指定在迭代器提前关闭时执行的逻辑。执行迭代的结构在想让迭代器知 道它不想遍历到可迭代对象耗尽时,就可以“关闭”迭代器。
内置语言结构在发现还有更多值可以迭代,但不会消费(使用)这些值时,会自动调用 return()方法。
class IteratorMock {constructor() {this.value = 0;}[Symbol.iterator](){return {next:() => {const currentValue = this.value +=1return {done:currentValue > 10, value:currentValue}},return(){console.log('end!!!')return {done:true}}}}}const arr = new IteratorMock()for (const arrayKey of arr) {console.log(arrayKey) // 输出 1 到 10}const arr = new IteratorMock()let [a, b] = arr; // end!!
如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。
生成器
箭头函数不能用作生成器函数
调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与 迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器 开始或恢复执行。
yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。生成器函数在遇到 yield 关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。
使用 yield 实现输入和输出
除了可以作为函数的中间返回语句使用,yield 关键字还可以作为函数的中间参数使用。上一次让 生成器函数暂停的 yield 关键字会接收到传给 next()方法的第一个值。
function* gen(){var result = yield fetch('https://jsonplaceholder.typicode.com/todos/1');var result2 = yield fetch('https://jsonplaceholder.typicode.com/todos/2');console.log(result.title + ';' + result2.title);}var g = gen()var result = g.next()result.value.then(res => res.json()).then(res => g.next(res).value).then(res => res.json()).then(res => g.next(res))
yield *
可以使用星号增强 yield 的行为,让它能够迭代一个可迭代对象,从而一次产出一个值:
function* generatorFn() { yield* [1, 2, 3]; }
