跳到主要内容

迭代器 & 生成器

可迭代对象及其迭代器是 ES6 中的特性。Array、Set、Map 均是可迭代的。

生成器也是 ES6 的特性,主要用于简化迭代器的创建。

迭代器原理

迭代器对象

迭代器对象是指拥有 next() 方法的对象,该方法应当返回下一次的迭代结果对象。迭代结果对象应当具有 valuedone 属性,来表示迭代的值以及是否迭代完成:

let range = {
from: 0, to: 3, current: 0,
next() {
if (this.current <= this.to) {
return { value: this.current++ }
} else {
return { done: true }
}
}
}

range.next() // => {value: 0}
range.next() // => {value: 1}
range.next() // => {value: 2}
range.next() // => {value: 3}
range.next() // => {done: true}

可迭代对象

可迭代对象是指有名为 Symbol.iterator 方法的对象,该方法应当返回一个迭代器对象:

class Range {
constructor(from, to) {
this.from = from
this.to = to
}

[Symbol.iterator]() {
let current = this.from
let last = this.to
return {
next() {
if (current <= last) {
return { value: current++ }
} else {
return { done: true }
}
}
}
}
}

可以对可迭代对象使用 for-of 语法,以及展开解构语法:

let range = new Range(0, 3)

for (const n of range) {
console.log(n)
} // 依次打印 0,1,2,3

[...r] // => (4) [0, 1, 2, 3]

let [a, b] = r // a = 0, b = 1

为了方便,可以让迭代器对象自身也为可迭代对象:

let iterator = {
next() { /* ... */ }
[Symbol.iterator]() { return this }
}

通过可迭代方法,可以实现无限的迭代(最简单的实现就是永远返回一个 value 属性)。如果对其使用展开语法,则会一直循环到内存耗尽,程序崩溃为止。

在一些情况下,比如迭代器可能会先读取文件,然后迭代,假如在跑到迭代结束前就已经停止了迭代(比如在 for-of 中使用了 break 退出),则不会处理后续的文件关闭。可以给迭代器对象定义 return() 方法,来执行一些清理工作。

生成器

生成器可以用来自定义迭代算法。需要通过生成器函数来定义一个生成器,它使用 function* 声明,用 yield 关键字来指明迭代值:

function* range(from, to) {
for (let i = from; i <= to; i += 1) {
yield i
}
}

生成器自身既是可迭代对象,又是迭代器对象

[...range(0, 3)]    // => (4) [0, 1, 2, 3]

let r = range(0, 3)
r.next() // => {value: 0, done: false}
r.next() // => {value: 1, done: false}
r.next() // => {value: 2, done: false}
r.next() // => {value: 3, done: false}
r.next() // => {value: undefined, done: true}

如果是对象方法,则只需在方法名前加上 * 号:

let obj = {
*range() { /* ... */ }
}

yield 关键字只能在生成器函数中使用,这样的写法是 错误 的:

function* range(arr) {
arr.forEach(e => yield e) // 实参应当为一个常规函数
}

可以用 yield* 来委托迭代可迭代对象(包括生成器):

function* rangeAll(...ranges) {
for (const range of ranges) {
yield* range
}
}

[...rangeAll(range(0, 3), range(10, 13))] // => (8) [0, 1, 2, 3, 10, 11, 12, 13]

生成器函数的返回值

生成器函数可以有 return 语句,其对应的迭代结果为:value 为返回值,donetrue

function* oneDone() {
yield 1
return 0
}

[...oneDone()] // => (1) [1]

let g = oneDone()
g.next() // => {value: 1, done: false}
g.next() // => {value: 0, done: true}
g.next() // => {value: undefined, done: true}

yield 表达式的值

在调用生成器的 next() 方法时可以传入参数,它会成为上次暂停的 yield 表达式的值:

function* oneAndEcho() {
let x = yield 1
yield x
}

let g = oneAndEcho()
g.next() // => {value: 1, done: false}
g.next("hello") // => {value: "hello", done: false}

生成器的 return() 和 throw() 方法

可以用生成器的 return()throw() 方法来对迭代进行终止或抛出异常。在生成器中可以用 try-catch-finally 语句,来应对这些方法调用,通常是做些文件关闭等清理工作。

参考

迭代器和生成器 - MDN

Symbol.iterator - MDN

迭代协议 - MDN

Generator - MDN