JavaScript 核心机制

迭代器&生成器

Symbol.iterator 协议async generator 异步迭代惰性求值与数据流处理—— 掌控数据流的终极武器。

Symbol.iterator 协议
Generator & yield
Async Generator
惰性数据流

L1 · 一句话理解

🎵
生活类比

迭代器就像一个音乐播放列表:你不用把所有歌一次性加载进内存, 而是每次按「下一首」时,才从列表里取出下一首歌。

生成器则是创建这个播放列表的工具——它不预先计算所有歌曲, 而是你每次按「下一首」时,它才「生成」下一首给你。

核心协议 · 一图流
Iterable[Symbol.iterator]Iterator
Iterator.next(){ value, done }
{ value, done }done === true?停止迭代
只要一个对象拥有 [Symbol.iterator] 方法, 它就是可迭代的 (Iterable),可以被 for...of 遍历。

L2 · 痛点:为什么需要迭代器协议?

😵

索引地狱

❌ 传统方式

每种数据结构有不同的遍历方式:数组用 for(i=0;i<n;i++),对象用 for...in,NodeList 用 forEach,Map 用 .entries()

✅ 迭代器方案

统一为 for...of,只要有 [Symbol.iterator] 就能遍历

💥

内存爆炸

❌ 传统方式

Array.from(range(1, 10_000_000)) 需要 ~80MB 内存存储一千万个数字。在浏览器主线程中直接 OOM。

✅ 迭代器方案

生成器按需生成,无论序列多长,内存始终 O(1)

🧩

缺乏组合性

❌ 传统方式

数组的 .filter().map().slice() 会创建中间数组,3 次遍历 = 3 倍计算,且无法提前终止。

✅ 迭代器方案

生成器链式组合,单次遍历 + 短路求值,零中间分配

L3 · 核心原理深度拆解

1

Symbol.iterator 协议 —— 统一的遍历契约

可迭代协议 (Iterable Protocol) 要求对象实现一个以 Symbol.iterator 为键的方法,该方法返回一个迭代器对象 (Iterator Object)。 迭代器对象必须有 .next() 方法,返回 { value, done } 结构。

JavaScript 内置的可迭代类型包括:ArrayStringMapSetTypedArrayargumentsNodeList。它们都实现了 [Symbol.iterator]

验证内置可迭代类型typescript
const arr = [10, 20, 30];
const iter = arr[Symbol.iterator](); // ← 获取迭代器

console.log(iter.next()); // { value: 10, done: false }
console.log(iter.next()); // { value: 20, done: false }
console.log(iter.next()); // { value: 30, done: false }
console.log(iter.next()); // { value: undefined, done: true }

// for...of 的本质就是调用 Symbol.iterator
// 等价于:
const iter2 = arr[Symbol.iterator]();
let result;
while (!(result = iter2.next()).done) {
  console.log(result.value); // 10, 20, 30
}
自定义可迭代对象:Range 类typescript
class Range {
  constructor(
    public start: number,
    public end: number,
    public step: number = 1
  ) {}

  // 实现可迭代协议
  [Symbol.iterator](): Iterator<number> {
    let current = this.start;
    const end = this.end;
    const step = this.step;

    return {
      next(): IteratorResult<number> {
        if (current <= end) {
          const value = current;  // ← 捕获当前值
          current += step;         // ← 推进指针
          return { value, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  }
}

// 使用方式与内置类型完全一致
const range = new Range(1, 10, 2);
for (const n of range) {
  console.log(n); // 1, 3, 5, 7, 9
}

// 展开运算符也生效(因为它是可迭代的)
const arr = [...new Range(1, 5)]; // [1, 2, 3, 4, 5]
2

Generator 函数 —— 自动实现迭代协议的语法糖

手写 [Symbol.iterator]非常繁琐。Generator(生成器函数)通过 function*语法和 yield关键字,自动生成迭代器对象,并管理执行状态。

Generator 三大特性
  • 1惰性执行:函数体不会立即执行,调用 .next() 才开始
  • 2可暂停:yield 让执行暂停,保留完整执行上下文(栈、局部变量)
  • 3双向通信:.next(value) 可向生成器传入值,替换 yield 表达式的返回值
⚡ 手写 vs Generator 对比
手写迭代器: ~15 行,需要维护闭包状态
Generator: ~5 行,yield 自动管理状态
同一个 Range,Generator 实现typescript
function* range(start: number, end: number, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;  // ← 暂停执行,返回 i 给调用者
  }
  // 函数结束时自动返回 { value: undefined, done: true }
}

// 直接用于 for...of
for (const n of range(1, 5)) {
  console.log(n); // 1, 2, 3, 4, 5
}

// 展开运算符
const nums = [...range(10, 15)]; // [10, 11, 12, 13, 14, 15]

// 解构赋值
const [first, second] = range(1, 100); // first=1, second=2
// ↑ 只迭代了 2 次就停止!这就是惰性的力量。

双向通信:.next(value) 传值回生成器

大多数人只知道 yield 向外输出值, 但它同时也是表达式——可以接收 .next(value) 传入的值。 这构成了生成器的双向数据通道

双向通信:暂停与恢复间传递数据typescript
function* conversation() {
  const name = yield "你叫什么名字?";  // ← yield 返回 .next() 传入的值
  const age  = yield `你好 ${name}, 你几岁了?`;
  return `${name} 今年 ${age} 岁。`;
}

const gen = conversation();

// 第一次 .next() 启动生成器,传入的值会被忽略(没有 yield 在等待)
console.log(gen.next());        // { value: "你叫什么名字?", done: false }

// 第二次 .next("小明") — "小明" 会成为第一个 yield 的返回值 → name = "小明"
console.log(gen.next("小明"));   // { value: "你好 小明, 你几岁了?", done: false }

// 第三次 .next(25) — 25 成为第二个 yield 的返回值 → age = 25
console.log(gen.next(25));      // { value: "小明 今年 25 岁。", done: true }
3

yield* 委托 —— 生成器的组合利器

树结构深度遍历

yield* 委托另一个可迭代对象,自动遍历其所有值。对于递归数据结构(如树), 它能让深度优先遍历变得异常优雅。

二叉树中序遍历 (yield*)typescript
type TreeNode<T> = {
  value: T;
  left?: TreeNode<T>;
  right?: TreeNode<T>;
};

function* inorder<T>(node?: TreeNode<T>): Generator<T> {
  if (!node) return;
  yield* inorder(node.left);   // ← 委托左子树
  yield node.value;             // ← 访问当前节点
  yield* inorder(node.right);   // ← 委托右子树
}

const tree: TreeNode<number> = {
  value: 4,
  left:  { value: 2, left: { value: 1 }, right: { value: 3 } },
  right: { value: 6, left: { value: 5 }, right: { value: 7 } },
};

console.log([...inorder(tree)]);
// [1, 2, 3, 4, 5, 6, 7] ← 中序遍历结果

生成器组合:链式数据流

yield* 让多个生成器可以像管道一样组合——每个生成器只负责一个独立的转换步骤, 实现了单一职责原则

链式数据流管道typescript
function* filter<T>(
  iter: Iterable<T>,
  predicate: (item: T) => boolean
): Generator<T> {
  for (const item of iter) {
    if (predicate(item)) yield item; // ← 只 yield 满足条件的
  }
}

function* map<T, U>(
  iter: Iterable<T>,
  transform: (item: T) => U
): Generator<U> {
  for (const item of iter) {
    yield transform(item);           // ← 转换后输出
  }
}

// 组合使用:先过滤,再映射
function* pipeline(source: Iterable<number>) {
  const evens = filter(source, n => n % 2 === 0);
  yield* map(evens, n => n ** 2);   // ← yield* 委托
}

const result = [...pipeline(range(1, 10))];
// range → [1,2,3,4,5,6,7,8,9,10]
// filter(even) → [2,4,6,8,10]
// map(n²) → [4,16,36,64,100]
console.log(result); // [4, 16, 36, 64, 100]
4

Async Generator —— 异步迭代与数据流

当数据源是异步的(网络请求、文件 I/O、WebSocket 消息)时, 我们需要异步生成器 (Async Generator)。 它结合了 Generator 的惰性与 Promise 的异步能力,通过 for await...of 消费。

同步迭代协议
Iterable: [Symbol.iterator]()
Iterator: .next() → {value, done}
消费: for...of
异步迭代协议
AsyncIterable: [Symbol.asyncIterator]()
AsyncIterator: .next() → Promise<{value, done}>
消费: for await...of
分页 API 自动拉取(异步生成器的杀手级应用)typescript
async function* fetchAllUsers(apiBase: string) {
  let page = 1;
  const perPage = 20;

  while (true) {
    const res = await fetch(                   // ← 异步等待网络请求
      `${apiBase}/users?page=${page}&per_page=${perPage}`
    );
    const data: User[] = await res.json();

    if (data.length === 0) return;             // ← 没有更多数据,结束生成器

    for (const user of data) {
      yield user;                              // ← 每个用户逐个 yield
    }

    page++;                                    // ← 自动翻页
    // 注意:如果一页数据不足 perPage,也可以提前结束
    if (data.length < perPage) return;
  }
}

// 使用 for await...of 消费 —— 代码极其简洁
async function main() {
  for await (const user of fetchAllUsers("https://api.example.com")) {
    console.log(user.name);
    // 处理到第 100 个就停止 → 自动停止请求后续页面
    // (这就是惰性的力量——未请求的页面永远不会被 fetch)
  }
}

高级模式:并发控制的异步任务池

异步生成器可以实现一个并发度可控的任务执行器——同时运行 N 个异步任务, 按完成顺序 yield 结果。这比 Promise.all更灵活,因为它允许消费者以不同速率处理结果。

并发任务池 — 按完成顺序 yieldtypescript
async function* asyncPool<T, R>(
  items: T[],
  concurrency: number,
  fn: (item: T) => Promise<R>
): AsyncGenerator<R> {
  const executing = new Set<Promise<void>>();

  for (const item of items) {
    const p = fn(item).then(result => {
      poolResult = result;       // 捕获结果
    });
    executing.add(p);

    p.finally(() => executing.delete(p));

    if (executing.size >= concurrency) {
      await Promise.race(executing);  // ← 等待任一任务完成
    }
  }
}

// 实际使用:并发 3 个请求,按完成顺序消费
async function* parallelFetch(urls: string[]) {
  const promises = urls.map(async (url) => {
    const start = Date.now();
    const res = await fetch(url);
    return { url, data: await res.json(), ms: Date.now() - start };
  });

  // 简化版:用 Promise.allSettled + 生成器包装
  for (const p of promises) {
    yield await p;  // ← 每个 promise resolve 后 yield
  }
}
5

惰性求值 —— Pull-based 数据流的精髓

Push vs Pull 模型对比

Push(推模式)· Observable / EventEmitter

生产者控制节奏。数据被主动推送给消费者, 消费者无法暂停。如果生产者太快,消费者可能被打爆。

Pull(拉模式)· Iterator / Generator

消费者控制节奏。数据在消费者调用 .next() 时才被计算。 天然背压 (backpressure) 机制——不拉就不生成。

惰性的性能实证:省了多少?

Eager vs Lazy:千万级数据处理对比typescript
// ❌ Eager(急切求值):创建 3 个中间数组
const huge = Array.from({ length: 10_000_000 }, (_, i) => i);
// 内存: ~80MB(存储 1000 万个数字)

const evens = huge.filter(n => n % 2 === 0);
// 内存: 再 ~40MB,遍历 1000 万次

const squared = evens.map(n => n * n);
// 内存: 再 ~40MB,遍历 500 万次

const first5 = squared.slice(0, 5);
// 总计: ~160MB,1500 万次计算,仅用 5 个值 💀

// ──────────────────────────────────────────────

// ✅ Lazy(惰性求值):零中间数组
function* range(n: number) {
  for (let i = 0; i < n; i++) yield i;
}
function* filter<T>(it: Iterable<T>, f: (v: T) => boolean) {
  for (const v of it) if (f(v)) yield v;
}
function* map<T, U>(it: Iterable<T>, f: (v: T) => U) {
  for (const v of it) yield f(v);
}
function* take<T>(it: Iterable<T>, n: number) {
  let i = 0;
  for (const v of it) { if (i++ >= n) return; yield v; }
}

const result = [...take(
  map(filter(range(10_000_000), n => n % 2 === 0), n => n * n),
  5
)];
// 内存: O(1),仅 ~30 次计算(找到前 5 个偶数的平方)
// 性能提升: ~500,000x ⚡

🎮 交互式实验场

Hands-on

生成器执行器

Interactive
const gen = counter();
function* counter() {← 执行到此
2 yield 1; // ①
3 yield 2; // ②
4 yield 3; // ③
5}
执行日志
点击 gen.next() 开始执行 ▲
关键洞察:生成器函数体不会一次性执行完毕。 每次调用 .next(), 函数从上次暂停处恢复,执行到下一个 yield 后再次暂停—— 这就是协程 (Coroutine) 的核心机制。

惰性流水线

Interactive
数据源:range(1, 30)共 30 个元素
点击添加管道操作 (最多 5 步)
← 点击上方按钮构建流水线

L4 · 生产级代码模式

Pattern 1

无限序列生成器

Fibonacci、质数、ID 生成器——无限序列是 Generator 最自然的用武之地。

Fibonacci 无限序列typescript
function* fibonacci(): Generator<number> {
  let [a, b] = [0, 1];
  while (true) {        // ← 无限循环!但不会阻塞
    yield a;
    [a, b] = [b, a + b];
  }
}

// 取前 10 个 Fibonacci 数
const fib10 = [...take(fibonacci(), 10)];
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// 找第一个超过 1000 的 Fibonacci 数
function* takeWhile<T>(
  iter: Iterable<T>,
  pred: (v: T) => boolean
): Generator<T> {
  for (const v of iter) {
    if (!pred(v)) return;
    yield v;
  }
}

const under1000 = [...takeWhile(fibonacci(), n => n < 1000)];
const first1000 = fibonacci();
// 手动遍历找到第一个 ≥ 1000 的
Pattern 2

状态机驱动的解析器

Generator 的暂停/恢复特性天然适合实现状态机——每次 yield 都是一次状态转换。

Tokenizer:将字符串拆分为 Tokentypescript
function* tokenize(input: string): Generator<{ type: string; value: string }> {
  let i = 0;
  while (i < input.length) {
    // 跳过空白
    if (/s/.test(input[i])) { i++; continue; }

    // 数字
    if (/d/.test(input[i])) {
      let num = "";
      while (i < input.length && /d/.test(input[i])) {
        num += input[i++];
      }
      yield { type: "NUMBER", value: num };  // ← 产出数字 Token
      continue;
    }

    // 运算符
    if ("+-*/".includes(input[i])) {
      yield { type: "OPERATOR", value: input[i] }; // ← 产出运算符 Token
      i++;
      continue;
    }

    throw new SyntaxError(`Unexpected char: ${input[i]}`);
  }
}

// 使用
const tokens = [...tokenize("3 + 4 * 2")];
// [{type:"NUMBER",value:"3"}, {type:"OPERATOR",value:"+"},
//  {type:"NUMBER",value:"4"}, {type:"OPERATOR",value:"*"},
//  {type:"NUMBER",value:"2"}]
Pattern 3 · 生产级

ReadableStream ↔ Async Generator 桥接

现代浏览器的 ReadableStream是 Push 模型,但通过 Async Generator 可以转换为 Pull 模型, 实现逐行读取大文件逐 chunk 处理响应体等场景。

将 ReadableStream 转为 Async Generatortypescript
async function* streamToAsyncGen<T>(
  stream: ReadableStream<T>
): AsyncGenerator<T> {
  const reader = stream.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) return;
      yield value;  // ← 每个 chunk 惰性 yield
    }
  } finally {
    reader.releaseLock();  // ← 确保释放锁
  }
}

// 使用场景:逐行读取 1GB CSV
async function processHugeCSV(url: string) {
  const res = await fetch(url);
  const stream = res.body!;  // ReadableStream<Uint8Array>
  const decoder = new TextDecoder();
  let lineCount = 0;

  for await (const chunk of streamToAsyncGen(stream)) {
    const text = decoder.decode(chunk, { stream: true });
    const lines = text.split("\n");
    for (const line of lines) {
      lineCount++;
      if (lineCount > 10000) return; // ← 随时停止
      await processLine(line);
    }
  }
}
React Server Component 中的流式渲染tsx
// 异步生成器配合 RSC 实现流式页面
async function* generatePageData(query: string) {
  // 第一批:立即返回缓存数据
  yield { type: "cache", data: await getCached(query) };

  // 第二批:数据库查询结果
  const dbResults = await db.search(query);
  yield { type: "db", data: dbResults };

  // 第三批:AI 增强结果(最慢)
  const aiEnhanced = await ai.enhance(dbResults);
  yield { type: "ai", data: aiEnhanced };
}

// 在 Server Component 中使用
export default async function SearchPage({
  searchParams,
}: {
  searchParams: { q: string };
}) {
  const batches = [];
  for await (const batch of generatePageData(searchParams.q)) {
    batches.push(batch);
  }
  return <SearchResults batches={batches} />;
}

L5 · 工程全景

O(1)
Generator 内存复杂度
无论序列多长,始终只保存当前状态
~500K×
take(5) 短路加速
10M 元素 + filter + map + take(5) 场景
~3×
单次 .next() 开销
比直接函数调用慢 ~3 倍(V8 优化后)

常见陷阱与反模式

陷阱 1:迭代器是一次性的
❌ 迭代器只能遍历一次typescript
const gen = range(1, 5);
const arr1 = [...gen]; // [1, 2, 3, 4, 5] ✅
const arr2 = [...gen]; // [] ❌ 空的!已经耗尽了

// ✅ 正确做法:每次需要时创建新的生成器
const arr3 = [...range(1, 5)]; // [1, 2, 3, 4, 5]
const arr4 = [...range(1, 5)]; // [1, 2, 3, 4, 5]
陷阱 2:生成器中的 this 指向
❌ 箭头函数不能做生成器typescript
// ❌ 箭头函数没有自己的 this,也不能用 function*
const obj = {
  items: [1, 2, 3],
  // 以下写法会报 SyntaxError
  // *getItems: () => { yield* this.items; }

  // ✅ 正确:使用方法简写
  *getItems() {
    for (const item of this.items) {
      yield item;
    }
  },
};

console.log([...obj.getItems()]); // [1, 2, 3]
陷阱 3:异步生成器不能用 for...of
⚠️ 混淆同步/异步迭代typescript
async function* asyncRange(n: number) {
  for (let i = 0; i < n; i++) {
    await delay(100);
    yield i;
  }
}

// ❌ 编译错误!异步迭代器不实现 [Symbol.iterator]
// for (const n of asyncRange(5)) { }

// ✅ 必须使用 for await...of
for await (const n of asyncRange(5)) {
  console.log(n); // 0, 1, 2, 3, 4(每个间隔 100ms)
}
陷阱 4:提前退出不触发 return
⚠️ 资源清理需要 finallytypescript
function* withResource() {
  const conn = acquireConnection(); // 获取资源
  try {
    for (const data of conn.query("SELECT *")) {
      yield data;
    }
  } finally {
    conn.close(); // ← 即使 break/return 也会执行
    console.log("连接已关闭");
  }
}

// break 会触发生成器的 .return() 方法
// 进而执行 finally 块
for (const row of withResource()) {
  if (row.id === 10) break; // ← 触发 finally
}

选型决策矩阵

特性Iterator / GeneratorObservable (RxJS)AsyncIterator
数据流方向Pull(拉取)Push(推送)Pull(异步拉取)
背压机制✅ 天然支持❌ 需手动处理✅ 天然支持
内存效率⭐⭐⭐ O(1)⭐⭐ 取决于订阅⭐⭐⭐ O(1)
组合性⭐⭐ yield*⭐⭐⭐ 操作符链⭐⭐ yield*
取消机制✅ .return()✅ unsubscribe✅ .return()
适用场景同步大数据流、解析器UI 事件流、实时数据API 分页、文件 I/O
学习曲线高(操作符多)
内置生态语言原生需引入 RxJS语言原生

⚡ 速查手册 Cheat Sheet

创建迭代器
const iter = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        return i < 3
          ? { value: i++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

💡 实现 [Symbol.iterator] 方法即可

Generator 基础
function* gen() {
  yield 1;
  yield 2;
  return 3; // done: true
}
const g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: true }

💡 function* + yield 创建生成器

yield* 委托
function* concat(...iters) {
  for (const iter of iters) {
    yield* iter; // 展开每个可迭代对象
  }
}

[...concat([1,2], [3,4], [5])];
// [1, 2, 3, 4, 5]

💡 yield* 委托给另一个可迭代对象

Async Generator
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const data = await fetch(url + page);
    const items = await data.json();
    if (!items.length) return;
    yield* items;
    page++;
  }
}

for await (const item of fetchPages(api)) {
  process(item); // 按需处理
}

💡 async function* + for await...of

双向通信
function* bidir() {
  const a = yield "first";  // 接收外部值
  const b = yield a;        // 再次接收
  return [a, b];
}

const g = bidir();
g.next();        // { value: "first" }
g.next("hello"); // { value: "hello" }
g.next("world"); // { value: ["hello","world"], done: true }

💡 .next(value) 可向生成器传值

实用工具函数
// take - 取前 n 个
function* take(it, n) {
  let i = 0;
  for (const v of it) {
    if (i++ >= n) return;
    yield v;
  }
}

// enumerate - 带索引迭代
function* enumerate(it) {
  let i = 0;
  for (const v of it) yield [i++, v];
}

[...enumerate("abc")];
// [[0,"a"],[1,"b"],[2,"c"]]

💡 组合工具构建数据处理管道

🎯

核心记忆锚点

迭代器协议是 JavaScript 的统一遍历契约。 Generator 是实现这个契约的最简语法糖。 惰性求值让你可以用 O(1) 内存处理无限序列。 Async Generator 把这个能力延伸到了异步数据流领域。 掌握它们,你就掌握了 JavaScript 数据处理的终极形态