Skip to content

JavaScript 生成器

什么是生成器

生成器(Generator)是 ES6(ECMAScript 2015)引入的一种特殊函数,它可以暂停执行并在稍后恢复执行。生成器函数使用 function* 语法定义,返回一个生成器对象,该对象实现了迭代器协议。

生成器的主要特点:

  • 可以暂停执行(使用 yield 语句)
  • 可以恢复执行(使用 next() 方法)
  • 可以向生成器传递值(通过 next() 方法的参数)
  • 可以抛出异常(使用 throw() 方法)
  • 实现了迭代器协议,可以在 for...of 循环中使用

生成器函数的定义

1. 基本语法

使用 function* 关键字定义生成器函数。

javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}

// 创建生成器对象
const generator = generatorFunction();

// 使用生成器
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

2. 生成器函数表达式

也可以使用函数表达式的形式定义生成器函数。

javascript
// 示例
const generatorFunction = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const generator = generatorFunction();

console.log(generator.next()); // { value: 1, done: false }

3. 生成器方法

在对象字面量和类中,可以定义生成器方法。

javascript
// 示例
// 对象字面量中的生成器方法
const obj = {
  *generatorMethod() {
    yield 1;
    yield 2;
  },
};

const generator1 = obj.generatorMethod();
console.log(generator1.next()); // { value: 1, done: false }

// 类中的生成器方法
class MyClass {
  *generatorMethod() {
    yield 1;
    yield 2;
  }
}

const instance = new MyClass();
const generator2 = instance.generatorMethod();
console.log(generator2.next()); // { value: 1, done: false }

生成器的工作原理

1. yield 语句

yield 语句用于暂停生成器的执行,并返回一个包含 valuedone 属性的对象。

  • value:yield 表达式的值
  • done:布尔值,表示生成器是否已完成执行
javascript
// 示例
function* generatorFunction() {
  console.log("开始执行");
  yield "第一个值";
  console.log("继续执行");
  yield "第二个值";
  console.log("执行完成");
}

const generator = generatorFunction();

console.log("创建生成器后");
console.log(generator.next()); // 开始执行,{ value: '第一个值', done: false }
console.log("第一次 next() 后");
console.log(generator.next()); // 继续执行,{ value: '第二个值', done: false }
console.log("第二次 next() 后");
console.log(generator.next()); // 执行完成,{ value: undefined, done: true }

2. next() 方法

next() 方法用于恢复生成器的执行,返回下一个 yield 表达式的值。

  • 第一次调用 next() 时,生成器开始执行,直到遇到第一个 yield 语句
  • 后续调用 next() 时,生成器从上次暂停的地方继续执行,直到遇到下一个 yield 语句或函数结束
javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
  return 4; // return 语句的值会作为最后一次 next() 的 value
}

const generator = generatorFunction();

console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: 4, done: true } // 注意:done 为 true

3. 向生成器传递值

可以通过 next() 方法的参数向生成器传递值,这个值会作为上一个 yield 表达式的返回值。

javascript
// 示例
function* generatorFunction() {
  const value1 = yield "请输入第一个值";
  console.log("收到第一个值:", value1);
  const value2 = yield "请输入第二个值";
  console.log("收到第二个值:", value2);
  return `两个值的和: ${value1 + value2}`;
}

const generator = generatorFunction();

console.log(generator.next()); // { value: '请输入第一个值', done: false }
console.log(generator.next(10)); // 收到第一个值: 10, { value: '请输入第二个值', done: false }
console.log(generator.next(20)); // 收到第二个值: 20, { value: '两个值的和: 30', done: true }

4. 抛出异常

可以使用 throw() 方法向生成器抛出异常,异常会在生成器暂停的地方被抛出。

javascript
// 示例
function* generatorFunction() {
  try {
    yield "正常执行";
    console.log("这里不会执行");
  } catch (error) {
    console.log("捕获到异常:", error.message);
    yield "从异常中恢复";
  }
}

const generator = generatorFunction();

console.log(generator.next()); // { value: '正常执行', done: false }
console.log(generator.throw(new Error("出错了"))); // 捕获到异常: 出错了, { value: '从异常中恢复', done: false }
console.log(generator.next()); // { value: undefined, done: true }

5. 生成器的返回值

生成器函数可以使用 return 语句返回值,这个值会作为最后一次 next() 调用的 value,但只有在 donetrue 时才会返回。

javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  return 3;
}

const generator = generatorFunction();

console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: true }

// 注意:在 for...of 循环中,return 的值不会被迭代
for (const value of generatorFunction()) {
  console.log(value); // 1, 2(不会输出 3)
}

生成器的应用场景

1. 迭代器

生成器实现了迭代器协议,可以轻松创建迭代器。

javascript
// 示例
// 创建一个范围迭代器
function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;
  }
}

// 使用 for...of 循环
for (const num of range(1, 5)) {
  console.log(num); // 1, 2, 3, 4, 5
}

// 转换为数组
const numbers = [...range(1, 10, 2)];
console.log(numbers); // [1, 3, 5, 7, 9]

// 使用解构赋值
const [first, second, ...rest] = range(1, 5);
console.log(first, second, rest); // 1 2 [3, 4, 5]

2. 异步编程

生成器可以用于简化异步编程,结合 yield 和 Promise 可以实现类似 async/await 的语法。

javascript
// 示例
// 模拟异步操作
function fetchData(url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`数据 from ${url}`);
    }, 1000);
  });
}

// 生成器函数
function* asyncGenerator() {
  console.log("开始");
  const data1 = yield fetchData("url1");
  console.log(data1);
  const data2 = yield fetchData("url2");
  console.log(data2);
  return "完成";
}

// 手动执行生成器
const generator = asyncGenerator();

function runGenerator(generator) {
  function process(result) {
    if (result.done) {
      console.log("最终结果:", result.value);
      return;
    }

    result.value
      .then((data) => {
        process(generator.next(data));
      })
      .catch((error) => {
        generator.throw(error);
      });
  }

  process(generator.next());
}

runGenerator(generator);
// 输出:
// 开始
// 数据 from url1
// 数据 from url2
// 最终结果: 完成

3. 无限序列

生成器可以用于创建无限序列,因为它们只在需要时生成值。

javascript
// 示例
// 生成无限的斐波那契数列
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
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5

// 生成无限的递增序列
function* increment(start = 0, step = 1) {
  let current = start;
  while (true) {
    yield current;
    current += step;
  }
}

const counter = increment(10, 5);
console.log(counter.next().value); // 10
console.log(counter.next().value); // 15
console.log(counter.next().value); // 20

4. 状态管理

生成器可以用于管理状态,因为它们可以在执行过程中保持状态。

javascript
// 示例
// 简单的状态机
function* stateMachine() {
  let state = "idle";
  while (true) {
    const action = yield state;
    switch (action) {
      case "start":
        state = "running";
        break;
      case "pause":
        state = "paused";
        break;
      case "stop":
        state = "stopped";
        break;
      default:
        state = "idle";
    }
  }
}

const machine = stateMachine();

console.log(machine.next().value); // 'idle'
console.log(machine.next("start").value); // 'running'
console.log(machine.next("pause").value); // 'paused'
console.log(machine.next("stop").value); // 'stopped'
console.log(machine.next("reset").value); // 'idle'

5. 惰性计算

生成器可以用于实现惰性计算,只在需要时才计算值。

javascript
// 示例
// 惰性计算平方数
function* squaredNumbers(numbers) {
  for (const num of numbers) {
    console.log(`计算 ${num} 的平方`);
    yield num * num;
  }
}

// 使用
const numbers = [1, 2, 3, 4, 5];
const squared = squaredNumbers(numbers);

// 只计算需要的值
console.log("取第一个值:");
console.log(squared.next().value); // 计算 1 的平方, 1

console.log("取第二个值:");
console.log(squared.next().value); // 计算 2 的平方, 4

// 剩余的值不会计算,除非显式请求

6. 协程

生成器可以用于实现协程,允许多个任务交替执行。

javascript
// 示例
// 协程示例
function* task1() {
  for (let i = 1; i <= 3; i++) {
    console.log(`任务1: 步骤 ${i}`);
    yield;
  }
}

function* task2() {
  for (let i = 1; i <= 3; i++) {
    console.log(`任务2: 步骤 ${i}`);
    yield;
  }
}

// 调度器
function scheduler(tasks) {
  const generators = tasks.map((task) => task());
  let current = 0;

  while (generators.some((gen) => !gen.next().done)) {
    current = (current + 1) % generators.length;
  }
}

// 执行
console.log("开始执行协程");
scheduler([task1, task2]);
// 输出:
// 任务1: 步骤 1
// 任务2: 步骤 1
// 任务1: 步骤 2
// 任务2: 步骤 2
// 任务1: 步骤 3
// 任务2: 步骤 3

生成器的方法

1. next(value)

恢复生成器的执行,返回下一个 yield 表达式的值。

  • 参数value - 作为上一个 yield 表达式的返回值
  • 返回值:包含 valuedone 属性的对象
javascript
// 示例
function* generatorFunction() {
  const value = yield "请输入值";
  return `你输入了: ${value}`;
}

const generator = generatorFunction();
console.log(generator.next()); // { value: '请输入值', done: false }
console.log(generator.next("Hello")); // { value: '你输入了: Hello', done: true }

2. throw(error)

向生成器抛出异常,异常会在生成器暂停的地方被抛出。

  • 参数error - 要抛出的异常对象
  • 返回值:与 next() 相同,返回下一个 yield 表达式的值
javascript
// 示例
function* generatorFunction() {
  try {
    yield "正常执行";
  } catch (error) {
    console.log("捕获到异常:", error.message);
    yield "从异常中恢复";
  }
}

const generator = generatorFunction();
console.log(generator.next()); // { value: '正常执行', done: false }
console.log(generator.throw(new Error("出错了"))); // 捕获到异常: 出错了, { value: '从异常中恢复', done: false }
console.log(generator.next()); // { value: undefined, done: true }

3. return(value)

终止生成器的执行,返回指定的值。

  • 参数value - 作为生成器的返回值
  • 返回值:包含 valuedone: true 的对象
javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = generatorFunction();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.return("结束")); // { value: '结束', done: true }
console.log(generator.next()); // { value: undefined, done: true }

生成器与迭代器的关系

1. 生成器实现了迭代器协议

生成器对象实现了迭代器协议,因此可以在任何需要迭代器的地方使用。

javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = generatorFunction();

// 使用迭代器协议
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

// 在 for...of 循环中使用
for (const value of generatorFunction()) {
  console.log(value); // 1, 2, 3
}

// 在扩展语法中使用
const array = [...generatorFunction()];
console.log(array); // [1, 2, 3]

// 在解构赋值中使用
const [a, b, c] = generatorFunction();
console.log(a, b, c); // 1, 2, 3

2. 自定义迭代器 vs 生成器

生成器提供了一种更简洁的方式来创建迭代器,相比自定义迭代器,代码更易读。

javascript
// 示例
// 自定义迭代器
const customIterator = {
  start: 1,
  end: 3,
  [Symbol.iterator]() {
    let current = this.start;
    return {
      next: () => {
        if (current <= this.end) {
          return { value: current++, done: false };
        } else {
          return { value: undefined, done: true };
        }
      },
    };
  },
};

// 使用自定义迭代器
for (const value of customIterator) {
  console.log(value); // 1, 2, 3
}

// 使用生成器创建迭代器
function* generatorIterator(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

// 使用生成器迭代器
for (const value of generatorIterator(1, 3)) {
  console.log(value); // 1, 2, 3
}

生成器的注意事项

1. 生成器函数的 this

生成器函数的 this 绑定与普通函数不同,需要特别注意。

javascript
// 示例
function* generatorFunction() {
  yield this.name;
}

const obj = {
  name: "John",
  generator: generatorFunction,
};

// 直接调用,this 指向全局对象
const generator1 = generatorFunction();
console.log(generator1.next().value); // undefined(在浏览器中)

// 作为对象方法调用,this 指向对象
const generator2 = obj.generator();
console.log(generator2.next().value); // 'John'

// 使用 bind 绑定 this
const boundGenerator = generatorFunction.bind({ name: "Jane" });
const generator3 = boundGenerator();
console.log(generator3.next().value); // 'Jane'

2. 生成器的状态

生成器对象是有状态的,一旦迭代完成,就不能重新开始。

javascript
// 示例
function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = generatorFunction();

// 迭代完成
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

// 不能重新开始,会一直返回 done: true
console.log(generator.next()); // { value: undefined, done: true }

// 需要创建新的生成器对象
const newGenerator = generatorFunction();
console.log(newGenerator.next()); // { value: 1, done: false }

3. 生成器的性能

生成器的性能比普通函数稍差,因为它们需要维护执行状态。在性能敏感的场景中,应该谨慎使用。

javascript
// 示例
// 性能测试
function* generatorFunction(n) {
  for (let i = 0; i < n; i++) {
    yield i;
  }
}

function regularFunction(n) {
  const result = [];
  for (let i = 0; i < n; i++) {
    result.push(i);
  }
  return result;
}

// 测试生成器性能
const n = 1000000;

console.time("generator");
const generator = generatorFunction(n);
for (const value of generator) {
  // 迭代
}
console.timeEnd("generator");

// 测试普通函数性能
console.time("regular");
const array = regularFunction(n);
for (const value of array) {
  // 迭代
}
console.timeEnd("regular");

// 结果:普通函数通常比生成器快

4. 生成器与箭头函数

箭头函数不能作为生成器函数,因为箭头函数没有自己的 this 绑定,也不能使用 function* 语法。

javascript
// 示例
// 错误:箭头函数不能作为生成器函数
// const generatorFunction = *() => {
//   yield 1;
// };

// 正确:使用普通函数
const generatorFunction = function* () {
  yield 1;
};

生成器的最佳实践

1. 合理使用生成器

生成器适用于以下场景:

  • 需要惰性计算的场景
  • 需要无限序列的场景
  • 需要状态管理的场景
  • 需要简化异步编程的场景

对于简单的同步操作,普通函数通常更高效。

2. 结合 Promise 使用

生成器与 Promise 结合使用,可以实现类似 async/await 的语法,使异步代码更易读。

javascript
// 示例
// 模拟异步操作
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 生成器函数
function* asyncTask() {
  console.log("开始");
  yield delay(1000);
  console.log("1秒后");
  yield delay(1000);
  console.log("2秒后");
  return "完成";
}

// 执行生成器
function run(generator) {
  return new Promise((resolve, reject) => {
    function process(result) {
      if (result.done) {
        resolve(result.value);
        return;
      }

      result.value
        .then((value) => process(generator.next(value)))
        .catch((error) => generator.throw(error));
    }

    try {
      process(generator.next());
    } catch (error) {
      reject(error);
    }
  });
}

// 使用
run(asyncTask()).then((result) => {
  console.log(result); // '完成'
});

3. 使用生成器创建迭代器

生成器是创建迭代器的简便方法,特别是对于复杂的迭代逻辑。

javascript
// 示例
// 生成器创建迭代器
function* createIterator(data) {
  // 复杂的迭代逻辑
  for (let i = 0; i < data.length; i++) {
    if (data[i] % 2 === 0) {
      yield data[i];
    }
  }
}

// 使用
const data = [1, 2, 3, 4, 5, 6];
for (const value of createIterator(data)) {
  console.log(value); // 2, 4, 6
}

4. 错误处理

在生成器中,应该使用 try-catch 捕获异常,确保生成器可以从错误中恢复。

javascript
// 示例
function* generatorFunction() {
  try {
    // 可能出错的代码
    const result = yield someAsyncOperation();
    console.log(result);
  } catch (error) {
    // 错误处理
    console.error("错误:", error);
    // 可以选择从错误中恢复
    yield "错误已处理";
  }
}

const generator = generatorFunction();
generator.next();
generator.throw(new Error("操作失败"));

生成器的兼容性

1. 浏览器兼容性

浏览器版本支持情况
Chrome39+支持
Firefox26+支持
Safari10+支持
Edge13+支持
IE所有版本不支持

2. Node.js 兼容性

Node.js 版本支持情况
4.0+支持

3. 转译工具

对于不支持生成器的环境,可以使用 Babel 等转译工具进行转译。

javascript
// Babel 配置示例 (.babelrc)
{
  "presets": ["@babel/preset-env"]
}

总结

生成器是 JavaScript 中强大的特性,它允许函数暂停执行并在稍后恢复执行。生成器的主要特点包括:

  1. 暂停和恢复:使用 yield 语句暂停执行,使用 next() 方法恢复执行
  2. 值传递:可以通过 next() 方法的参数向生成器传递值
  3. 异常处理:可以使用 throw() 方法向生成器抛出异常
  4. 迭代器协议:生成器对象实现了迭代器协议,可以在 for...of 循环中使用
  5. 惰性计算:只在需要时生成值,适合处理大型数据集或无限序列

生成器的应用场景非常广泛,包括:

  • 创建迭代器
  • 简化异步编程
  • 生成无限序列
  • 管理状态
  • 实现惰性计算
  • 实现协程

虽然生成器的性能比普通函数稍差,但在适当的场景中使用,它们可以使代码更易读、更易维护。在现代 JavaScript 中,async/await 语法在很多情况下已经替代了生成器用于异步编程,但生成器仍然是处理迭代和状态管理的强大工具。

通过理解和掌握生成器,我们可以编写更加灵活、高效的 JavaScript 代码。