迭代器 & 生成器
可迭代对象及其迭代器是 ES6 中的特性。Array、Set、Map 均是可迭代的。
生成器也是 ES6 的特性,主要用于简化迭代器的创建。
迭代器原理
迭代器对象
迭代器对象是指拥有 next()
方法的对象,该方法应当返回下一次的迭代结果对象。迭代结果对象应当具有 value
或 done
属性,来表示迭代的值以及是否迭代完成:
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
为返回值,done
为 true
:
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 语句,来应对这些方法调用,通常是做些文件关闭等清理工作。