迭代器&生成器
L1 · 一句话理解
L2 · 痛点:为什么需要迭代器协议?
L3 · 核心原理深度拆解
Symbol.iterator 协议 —— 统一的遍历契约
可迭代协议 (Iterable Protocol) 要求对象实现一个以 Symbol.iterator 为键的方法,该方法返回一个迭代器对象 (Iterator Object)。 迭代器对象必须有 .next() 方法,返回 { value, done } 结构。
JavaScript 内置的可迭代类型包括:Array、String、Map、Set、TypedArray、arguments、NodeList。它们都实现了 [Symbol.iterator]。
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
}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]Generator 函数 —— 自动实现迭代协议的语法糖
手写 [Symbol.iterator]非常繁琐。Generator(生成器函数)通过 function*语法和 yield关键字,自动生成迭代器对象,并管理执行状态。
- 1惰性执行:函数体不会立即执行,调用 .next() 才开始
- 2可暂停:yield 让执行暂停,保留完整执行上下文(栈、局部变量)
- 3双向通信:.next(value) 可向生成器传入值,替换 yield 表达式的返回值
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) 传入的值。 这构成了生成器的双向数据通道。
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 }yield* 委托 —— 生成器的组合利器
树结构深度遍历
yield* 委托另一个可迭代对象,自动遍历其所有值。对于递归数据结构(如树), 它能让深度优先遍历变得异常优雅。
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* 让多个生成器可以像管道一样组合——每个生成器只负责一个独立的转换步骤, 实现了单一职责原则。
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]Async Generator —— 异步迭代与数据流
当数据源是异步的(网络请求、文件 I/O、WebSocket 消息)时, 我们需要异步生成器 (Async Generator)。 它结合了 Generator 的惰性与 Promise 的异步能力,通过 for await...of 消费。
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更灵活,因为它允许消费者以不同速率处理结果。
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
}
}惰性求值 —— Pull-based 数据流的精髓
Push vs Pull 模型对比
生产者控制节奏。数据被主动推送给消费者, 消费者无法暂停。如果生产者太快,消费者可能被打爆。
消费者控制节奏。数据在消费者调用 .next() 时才被计算。 天然背压 (backpressure) 机制——不拉就不生成。
惰性的性能实证:省了多少?
// ❌ 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.next(), 函数从上次暂停处恢复,执行到下一个 yield 后再次暂停—— 这就是协程 (Coroutine) 的核心机制。惰性流水线
Interactiverange(1, 30)共 30 个元素L4 · 生产级代码模式
无限序列生成器
Fibonacci、质数、ID 生成器——无限序列是 Generator 最自然的用武之地。
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 的状态机驱动的解析器
Generator 的暂停/恢复特性天然适合实现状态机——每次 yield 都是一次状态转换。
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"}]ReadableStream ↔ Async Generator 桥接
现代浏览器的 ReadableStream是 Push 模型,但通过 Async Generator 可以转换为 Pull 模型, 实现逐行读取大文件、逐 chunk 处理响应体等场景。
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);
}
}
}// 异步生成器配合 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 · 工程全景
常见陷阱与反模式
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]// ❌ 箭头函数没有自己的 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]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)
}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 / Generator | Observable (RxJS) | AsyncIterator |
|---|---|---|---|
| 数据流方向 | Pull(拉取) | Push(推送) | Pull(异步拉取) |
| 背压机制 | ✅ 天然支持 | ❌ 需手动处理 | ✅ 天然支持 |
| 内存效率 | ⭐⭐⭐ O(1) | ⭐⭐ 取决于订阅 | ⭐⭐⭐ O(1) |
| 组合性 | ⭐⭐ yield* | ⭐⭐⭐ 操作符链 | ⭐⭐ yield* |
| 取消机制 | ✅ .return() | ✅ unsubscribe | ✅ .return() |
| 适用场景 | 同步大数据流、解析器 | UI 事件流、实时数据 | API 分页、文件 I/O |
| 学习曲线 | 低 | 高(操作符多) | 低 |
| 内置生态 | 语言原生 | 需引入 RxJS | 语言原生 |
⚡ 速查手册 Cheat Sheet
核心记忆锚点
迭代器协议是 JavaScript 的统一遍历契约。 Generator 是实现这个契约的最简语法糖。 惰性求值让你可以用 O(1) 内存处理无限序列。 Async Generator 把这个能力延伸到了异步数据流领域。 掌握它们,你就掌握了 JavaScript 数据处理的终极形态。