前言
在 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) 的一种实现。
- 保存上下文:当函数遇到
yield暂停时,它的执行上下文(变量、栈帧)会被保存在内存中。 - 让出主线程:此时主线程可以去干别的事,直到被
next()唤醒。 - async/await 的原型:实际上,
async/await只是Generator+Promise的语法糖。
五、 面试模拟题
Q1:生成器函数和普通函数有什么区别?
参考回答:
- 执行方式:普通函数一调到底;生成器函数返回一个迭代器对象,需手动调用
next()。 - 暂停能力:生成器可以使用
yield暂停,普通函数不行。 - 返回值:普通函数返回具体值或
undefined;生成器返回一个包含value和done属性的对象。
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 表达式的结果 |