Skip to content

生成器

生成器函数是一种“按需产出值”的函数。它不会一次性把结果全部算完,而是每次在需要时再继续执行。

生成器和普通函数的区别

普通函数执行后直接返回一个结果:

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
}

这让函数不再是“一次执行到底”,而是变成“分段执行”。

returnyield 的区别

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 当成普通产出值处理。
  • 如果逻辑只是一次性算完返回结果,普通函数通常更直接。

基于 MIT 许可发布