【学习记录】JavaScript 的 Generator 详解

449 阅读3分钟

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 }
  • valueyield 表达式的返回值。
  • 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 可以用于简化异步编程,结合 Promiseyield 可以实现类似 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 的应用场景

  1. 惰性计算: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
    
  2. 异步流程控制:结合 Promiseyield,可以简化异步代码。

  3. 自定义迭代器:Generator 可以轻松实现自定义迭代器。

  4. 状态机:Generator 可以用于实现状态机,每个 yield 表示一个状态。

7. Generator 的注意事项

  • Generator 函数不能使用箭头函数定义(() => {}),因为箭头函数没有自己的 thisarguments
  • 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 的语法糖,提供了更简洁的异步编程方式。