主题
生成器
生成器函数是一种“按需产出值”的函数。它不会一次性把结果全部算完,而是每次在需要时再继续执行。
生成器和普通函数的区别
普通函数执行后直接返回一个结果:
js
function sum() {
return 3
}生成器函数则可以多次产出值:
js
function* generateSequence() {
yield 1
yield 2
yield 3
}这里的 function* 和 yield 是两个关键点。
调用生成器不会立刻执行函数体
js
function* generateSequence() {
console.log('start')
yield 1
}
const generator = generateSequence()执行 generateSequence() 之后,拿到的是一个生成器对象,不是最终值,函数体也不会立刻一路跑完。
生成器对象本身也是迭代器
js
function* generateSequence() {
yield 1
yield 2
}
const generator = generateSequence()
console.log(generator.next()) // { value: 1, done: false }
console.log(generator.next()) // { value: 2, done: false }
console.log(generator.next()) // { value: undefined, done: true }每次调用 next(),生成器都会从上一次暂停的位置继续执行。
yield 的作用
yield 可以理解为“产出一个值,并在这里暂停”:
js
function* generateSequence() {
yield 1
yield 2
yield 3
}这让函数不再是“一次执行到底”,而是变成“分段执行”。
return 和 yield 的区别
yield 产出的值会参与迭代,return 则表示生成器结束:
js
function* generateSequence() {
yield 1
yield 2
return 3
}
const generator = generateSequence()
console.log(generator.next()) // { value: 1, done: false }
console.log(generator.next()) // { value: 2, done: false }
console.log(generator.next()) // { value: 3, done: true }这里要特别注意:for...of 不会遍历 done: true 那一步的 value。
js
function* generateSequence() {
yield 1
yield 2
return 3
}
for (const value of generateSequence()) {
console.log(value) // 1 2
}如果希望 3 也参与迭代,就应该写成 yield 3。
生成器天然就是 iterable
生成器对象可以直接被 for...of 遍历:
js
function* generateSequence() {
yield 1
yield 2
yield 3
}
for (const value of generateSequence()) {
console.log(value)
}这也是生成器经常被拿来实现自定义 iterable 的原因。
一个典型场景:生成序列
js
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i
}
}
console.log([...range(1, 5)]) // [1, 2, 3, 4, 5]这种写法通常比手动实现 Symbol.iterator + next() 更直观。
yield*
如果想把另一个 iterable 继续展开产出,可以使用 yield*:
js
function* generate() {
yield 1
yield* [2, 3]
yield 4
}
console.log([...generate()]) // [1, 2, 3, 4]这适合把多个序列拼起来。
next(value) 可以向生成器传值
生成器不只是“往外吐值”,也能在恢复执行时接收外部传入的数据:
js
function* gen() {
const name = yield 'input name'
return `hello ${name}`
}
const iterator = gen()
console.log(iterator.next()) // { value: 'input name', done: false }
console.log(iterator.next('Lee')) // { value: 'hello Lee', done: true }这说明生成器本质上是一种可暂停、可恢复、可双向通信的执行模型。
什么时候适合用生成器
生成器更适合:
- 惰性生成数据
- 自定义 iterable
- 表达分段执行过程
- 避免一次性创建大批结果
如果只是普通函数返回一个数组,没必要强行改成生成器。
使用建议
- 需要“按需产出值”时,再考虑生成器。
- 想实现 iterable 时,优先考虑生成器写法,通常比手写迭代器更清晰。
- 看到
return出现在生成器里时,要记得它不会被for...of当成普通产出值处理。 - 如果逻辑只是一次性算完返回结果,普通函数通常更直接。
