人人都会AI编程

11.4 迭代器与生成器(Iterator / Generator)

更新时间:2026-07-11

遍历数据是编程中最常见的操作之一。ES6 引入的迭代器与生成器,为 JavaScript 提供了一套统一、可扩展的数据遍历机制,让自定义数据结构也能轻松融入 for...of、展开运算符等语法,同时也为异步流程控制带来了新的可能性。

11.4.1 迭代协议:统一的遍历约定

迭代协议是迭代器与生成器的底层基础,它定义了“怎样才算是可遍历的”以及“怎样进行遍历”。

可迭代对象(Iterable)
一个对象要想成为可迭代对象,必须实现 Symbol.iterator 方法。该方法返回一个迭代器对象。

本质上,任何实现了 [Symbol.iterator]() { ... } 结构的对象,都可以被 for...of 循环消费。数组、字符串、Map、Set 等内置数据结构都已经遵循了迭代协议,因此我们才能直接用 for...of 遍历它们。

const arr = [1, 2, 3];
// arr 内置了 Symbol.iterator 方法
for (const item of arr) {
  console.log(item); // 1, 2, 3
}

迭代器对象(Iterator)
迭代器对象必须提供一个 next() 方法,该方法返回形如 { value: any, done: boolean } 的结果:

  • value 表示当前迭代的值
  • donetrue 表示遍历已经结束

调用一次 next(),迭代器向前“推进”一步。这种惰性求值的方式意味着数据不需要一次性全部生成,可以按需计算,节省内存。

// 手动调用迭代器
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

11.4.2 自定义可迭代对象

掌握了迭代协议,我们可以让任何对象都支持 for...of。比如实现一个简单的范围迭代器:

const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

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

for...of、展开运算符 [...obj]Array.from()、解构赋值等语法,都依赖迭代协议。当你将对象实现了 Symbol.iterator,这些工具将自动生效,无需额外改造。

11.4.3 Generator:迭代器的语法糖

手动维护迭代器中的状态(如上例中的 current)有时会比较繁琐,特别是当遍历逻辑复杂时。Generator 函数正是为了解决这一痛点而生的——它可以让你用更简洁的代码来生成一系列值,并且隐式地遵循了迭代协议

定义与执行
Generator 函数使用 function* 声明,内部通过 yield 关键字“暂停”执行并产出一个值。

function* countUpTo(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const gen = countUpTo(3);
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 }

当调用 countUpTo(3) 时,并不会立即执行函数体,而是返回一个迭代器对象。每次调用该迭代器的 next(),Generator 函数体才会执行到下一个 yield 语句,然后暂停,并将 yield 后面的值作为 next() 的返回值。这一特性称为协程式的执行——函数可以在中途暂停,然后再被唤醒继续执行,同时保留其局部状态。

yield 的双向通信
yield 不仅能向外产出值,还能从外部接收值。调用 next() 时可以传入参数,这个参数会成为上一个 yield 表达式的返回值。

function* conversation() {
  const name = yield "What is your name?";
  yield `Hello, ${name}!`;
}

const talk = conversation();
console.log(talk.next().value);      // "What is your name?"
console.log(talk.next("Alice").value); // "Hello, Alice!"

这一特性让 Generator 能够实现比普通迭代器更复杂的控制流,比如状态机、任务调度等。

11.4.4 异步迭代与 for...of

Generator 本身是同步的,但在实际开发中,我们经常需要异步地拿到数据(比如分批读取数据库)。ES2018 引入了异步迭代器(Symbol.asyncIterator)和 for await...of 循环,来处理异步数据流。而 Generator 还有异步版本:async function*,结合 yield 可以产出 Promise,配合 for await...of 实现极大简化。

// 模拟分页获取数据的异步生成器
async function* fetchPages(url, totalPages) {
  for (let i = 1; i <= totalPages; i++) {
    const response = await fetch(`${url}?page=${i}`);
    const data = await response.json();
    yield data;
  }
}

(async () => {
  for await (const pageData of fetchPages('/api/posts', 3)) {
    console.log(pageData); // 依次输出每页数据
  }
})();

for await...of 会自动等待每个 Promise 解析完成,再继续下一次迭代。这种模式在处理流式数据、大数据集或网络分页时非常实用。

11.4.5 实际应用场景

1. 自定义数据结构的遍历
当你的类需要一种自然的遍历接口时,实现 Generator 版本的 Symbol.iterator 能让代码简洁优雅。

class Stack {
  constructor() {
    this.items = [];
  }
  push(...vals) {
    this.items.push(...vals);
  }
  *[Symbol.iterator]() {
    for (let i = this.items.length - 1; i >= 0; i--) {
      yield this.items[i];
    }
  }
}

2. 惰性求值与无限序列
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

3. 异步流程控制
async/await 普及之前,Generator 曾是处理异步的核心手段(例如 Redux-Saga 就大量使用了 Generator)。即使今天,使用 Generator 管理多步骤任务、实现取消和暂停,仍然是某些库的底层选择。

4. 实现自定义控件遍历
业务中需要按特定顺序遍历多层嵌套数据时,Generator 能把复杂的递归逻辑拆解为平铺的 yield 序列,代码可读性显著提升。

11.4.6 小结

迭代器与生成器并不是一门新语言,而是对“遍历”这一行为的标准化。迭代协议让我们得以用统一的方式消费数据结构,而 Generator 则让编写迭代器变得轻松且表达能力更强。理解这两个概念,你不仅能写出更干净的代码,还能更深入地理解 for...of、展开运算符以及现代前端框架中状态流转机制的底层原理。在需要处理惰性计算、异步流控、复杂遍历等场景时,它们将是最得力的工具。