JavaScript 中的 Generator 函数(生成器函数)是一种特殊类型的函数,它允许你定义一个可暂停和恢复执行的函数。这是 ES6(ECMAScript 2015)引入的重要特性之一,主要用于实现惰性求值、异步编程、迭代器模式等场景。
一、基本语法
定义方式
使用 function* 关键字定义:
js
编辑
function* myGenerator() {
yield 1;
yield 2;
return 3;
}
function*:声明一个生成器函数。yield:暂停函数执行,并返回一个值;下次调用.next()时从该位置继续执行。return:结束生成器,返回最终值(之后再调用.next()返回{ done: true, value: undefined })。
二、调用与执行
生成器函数不会像普通函数那样立即执行,而是返回一个 迭代器对象(Iterator) :
js
编辑
const gen = myGenerator(); // 不执行函数体,只返回迭代器
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }
每次调用 .next():
- 恢复执行,直到遇到下一个
yield或return。 - 返回一个对象
{ value: ..., done: ... }。
三、yield 表达式的双向通信
yield 不仅可以“产出”值,还可以“接收”外部传入的值:
js
编辑
function* echo() {
const input1 = yield '请输入第一个值';
console.log('收到:', input1);
const input2 = yield '请输入第二个值';
console.log('收到:', input2);
}
const e = echo();
console.log(e.next()); // { value: "请输入第一个值", done: false }
console.log(e.next('Hello')); // 收到: Hello → { value: "请输入第二个值", done: false }
console.log(e.next('World')); // 收到: World → { value: undefined, done: true }
注意:第一次调用
.next()时传参是无效的(因为还没遇到第一个yield)。
四、与迭代协议(Iterable Protocol)结合
生成器函数天然实现了 可迭代协议([Symbol.iterator]) ,因此可以直接用于 for...of 循环:
js
编辑
function* countUpTo(max) {
for (let i = 1; i <= max; i++) {
yield i;
}
}
for (const num of countUpTo(3)) {
console.log(num); // 1, 2, 3
}
注意:
for...of会自动忽略return的值(因为它在done: true之后),只遍历yield的值。
五、错误处理
可以在生成器内部或外部抛出/捕获错误:
1. 外部向生成器抛出错误:
js
编辑
function* gen() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('捕获错误:', e.message);
}
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
g.throw(new Error('出错了!')); // 捕获错误: 出错了! → { value: undefined, done: true }
2. 生成器内部抛出错误:
js
编辑
function* badGen() {
yield 1;
throw new Error('内部错误');
}
const bg = badGen();
bg.next(); // { value: 1, done: false }
bg.next(); // 抛出异常!需用 try/catch 包裹
六、实际应用场景
1. 惰性序列生成
js
编辑
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// 可无限生成,但只在需要时计算
2. 简化异步流程控制(配合 Promise)
虽然现代 JS 更多用 async/await,但早期常用生成器 + co 库实现异步:
js
编辑
function* asyncTask() {
const data = yield fetch('/api/data').then(r => r.json());
console.log(data);
}
// 需要一个运行器(如 co)来自动处理 .next() 和 Promise
3. 状态机
js
编辑
function* trafficLight() {
while (true) {
yield 'green';
yield 'yellow';
yield 'red';
}
}
const light = trafficLight();
console.log(light.next().value); // green
console.log(light.next().value); // yellow
console.log(light.next().value); // red
七、与 async/await 的关系
-
相似点:都能暂停/恢复执行。
-
不同点:
async/await是基于 Promise 的语法糖,专为异步设计。- Generator 更通用,可用于迭代、状态机、协程等。
await自动等待 Promise,而yield需要手动处理(除非配合库如co)。
八、注意事项
- 生成器函数不能作为构造函数(不能用
new调用)。 - 一旦
done: true,后续.next()不会再执行函数体。 - 生成器是一次性的:不能重置,若需重新开始,必须创建新实例。
总结
| 特性 | 说明 |
|---|---|
| 关键字 | function* |
| 控制流 | yield 暂停,.next() 恢复 |
| 返回值 | 迭代器对象(Iterator) |
| 通信 | 双向:yield 传出,.next(val) 传入 |
| 协议 | 实现了 Iterable 接口 |
| 应用 | 惰性计算、异步控制、状态机、自定义迭代 |
Generator 是 JavaScript 中非常强大且灵活的工具,理解它有助于深入掌握迭代器、异步编程和函数式编程思想。
如果你感兴趣,我也可以展示如何用 Generator 实现一个简单的协程调度器!