JS-深入浅出 Generator(yield/next)全解析

59 阅读3分钟

前言

在 JavaScript 中,函数一旦开始执行,通常会一走到底。但 Generator(生成器) 的出现打破了这一规则。它允许函数在执行过程中“暂停”,并在稍后从暂停的地方“恢复”。这种特性让它成为了处理复杂异步逻辑的神器。

一、 核心概念:什么是生成器?

生成器是一种特殊的函数,它可以在执行过程中暂停执行,并在稍后恢复。

  • 定义方式:在 function 关键字后加一个星号 *
  • 暂停键 yield:在函数内部,遇到 yield 关键字时,函数会暂停并将后面的值返回。
  • 启动键 next() :在函数外部,通过调用生成器对象的 next() 方法来恢复执行。

二、 基础实战:分段执行

生成器函数调用后不会立即执行,而是返回一个迭代器对象

function* genDemo() {
  console.log("--- 第 1 段执行 ---");
  yield 'A'; // 返回 'A' 并暂停

  console.log("--- 第 2 段执行 ---");
  yield 'B'; // 返回 'B' 并暂停

  console.log("--- 结束执行 ---");
  return 'C'; // 最终返回 'C',此时 done 为 true
}

const gen = genDemo();

console.log(gen.next()); // { value: 'A', done: false }
console.log(gen.next()); // { value: 'B', done: false }
console.log(gen.next()); // { value: 'C', done: true }

三、 进阶:双向数据传递

很多同学只知道 yield 能把值传出去,其实 next() 也可以把值传回函数内部。

关键点next(val) 传入的参数,会作为上一个 yield 语句的返回值。

function* chat() {
  const answer1 = yield "你叫什么名字?";
  console.log(`收到名字: ${answer1}`);
  
  const answer2 = yield "你今年多大了?";
  console.log(`收到年龄: ${answer2}`);
}

const it = chat();

// 1. 启动生成器,拿到第一个问题
console.log(it.next().value);    // "你叫什么名字?"
 
// 2. 传回名字,拿到第二个问题
//  收到名字: 阿强
console.log(it.next("阿强").value); // "你今年多大了?"

// 3. 传回年龄
it.next(25); 
// 收到年龄: 25

四、 为什么需要 Generator?(底层原理)

生成器是 协程(Coroutine) 的一种实现。

  1. 保存上下文:当函数遇到 yield 暂停时,它的执行上下文(变量、栈帧)会被保存在内存中。
  2. 让出主线程:此时主线程可以去干别的事,直到被 next() 唤醒。
  3. async/await 的原型:实际上,async/await 只是 Generator + Promise 的语法糖。

五、 面试模拟题

Q1:生成器函数和普通函数有什么区别?

参考回答:

  • 执行方式:普通函数一调到底;生成器函数返回一个迭代器对象,需手动调用 next()
  • 暂停能力:生成器可以使用 yield 暂停,普通函数不行。
  • 返回值:普通函数返回具体值或 undefined;生成器返回一个包含 valuedone 属性的对象。

Q2:如何自动执行生成器函数(不手动调 next)?

参考回答:

可以使用 for...of 循环,或者编写一个递归的 执行器函数(如 co 模块)。

function* count() {
  yield 1;
  yield 2;
  yield 3;
}
for (let v of count()) {
  console.log(v); // 1, 2, 3 (注意:for...of 忽略 return 的值)
}

Q3:yield 后面可以接什么?

参考回答:

可以接任何表达式。如果接的是另一个生成器函数,需要使用 yield*(委托 yield),这样可以实现生成器的嵌套。


六、 总结表

动作描述
yield x暂停函数执行,将 x 返回给外部 next().value
next(y)恢复执行,并将 y 作为函数内 yield 表达式的结果