JavaScript 的 Generator 详解
Generator(生成器)是 JavaScript 中一种特殊的函数,它可以通过 function* 语法定义,并使用 yield 关键字来暂停和恢复函数的执行。Generator 提供了一种更灵活的方式来控制函数的执行流程,特别适合处理异步操作、迭代器和惰性计算等场景。
1. 基本语法
Generator 函数使用 function* 定义,函数体内可以使用 yield 关键字来暂停执行并返回一个值。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
调用 Generator 函数时,它不会立即执行,而是返回一个 Generator 对象。这个对象实现了 迭代器协议,可以通过 next() 方法来逐步执行函数。
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: false }
console.log(gen.next()); // { value: undefined, done: true }
value:yield表达式的返回值。done:表示 Generator 函数是否执行完毕。
2. yield 关键字
yield 是 Generator 的核心关键字,它的作用如下:
- 暂停 Generator 函数的执行,并返回
yield后面的值。 - 当调用
next()时,Generator 会从上次暂停的地方继续执行。
function* generatorWithYield() {
const name = yield "What's your name?";
yield `Hello, ${name}!`;
}
const gen = generatorWithYield();
console.log(gen.next()); // { value: "What's your name?", done: false }
console.log(gen.next("Alice")); // { value: "Hello, Alice!", done: false }
console.log(gen.next()); // { value: undefined, done: true }
- 第一次调用
next()时,Generator 执行到第一个yield并暂停。 - 第二次调用
next("Alice")时,"Alice"会作为上一个yield的返回值赋值给name,然后继续执行到下一个yield。
3. yield* 关键字
yield* 用于委托给另一个 Generator 函数或可迭代对象(如数组、字符串等)。
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield* generator1(); // 委托给 generator1
yield 3;
}
const gen = generator2();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
4. Generator 与迭代器
Generator 对象实现了 迭代器协议,因此可以直接用于 for...of 循环或解构赋值。
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
for (const num of numberGenerator()) {
console.log(num); // 1, 2, 3
}
const [a, b, c] = numberGenerator();
console.log(a, b, c); // 1, 2, 3
5. Generator 与异步编程
Generator 可以用于简化异步编程,结合 Promise 和 yield 可以实现类似 async/await 的效果。
function* asyncGenerator() {
const result1 = yield fetchData1(); // 假设 fetchData1 返回一个 Promise
const result2 = yield fetchData2(result1); // 假设 fetchData2 返回一个 Promise
return result2;
}
// 手动执行 Generator
const gen = asyncGenerator();
gen.next().value.then((result1) => {
gen.next(result1).value.then((result2) => {
console.log(gen.next(result2)); // { value: result2, done: true }
});
});
为了更方便地处理这种场景,可以使用 co 库(一个 Generator 执行器):
const co = require("co");
co(function* () {
const result1 = yield fetchData1();
const result2 = yield fetchData2(result1);
console.log(result2);
});
6. Generator 的应用场景
-
惰性计算:Generator 可以按需生成值,适合处理大数据集或无限序列。
function* fibonacci() { let [a, b] = [0, 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 -
异步流程控制:结合
Promise和yield,可以简化异步代码。 -
自定义迭代器:Generator 可以轻松实现自定义迭代器。
-
状态机:Generator 可以用于实现状态机,每个
yield表示一个状态。
7. Generator 的注意事项
- Generator 函数不能使用箭头函数定义(
() => {}),因为箭头函数没有自己的this和arguments。 - Generator 函数执行后返回的是一个 Generator 对象,而不是函数返回值。
- Generator 对象的状态是不可逆的,一旦执行完毕(
done: true),就不能再恢复。
8. Generator 与 async/await 的关系
async/await 是 Generator 和 Promise 的语法糖。async 函数可以看作是一个自动执行的 Generator 函数,而 await 类似于 yield。
async function asyncFunction() {
const result1 = await fetchData1();
const result2 = await fetchData2(result1);
return result2;
}
总结
- Generator 是一种特殊的函数,通过
function*定义,使用yield暂停和恢复执行。 - Generator 对象实现了迭代器协议,可以用于
for...of循环或解构赋值。 - Generator 适合处理异步编程、惰性计算和自定义迭代器等场景。
async/await是 Generator 和Promise的语法糖,提供了更简洁的异步编程方式。