迭代器模式
问题
什么是迭代器模式?JavaScript 的 Iterator 协议和 Iterable 协议是什么?如何实现自定义迭代器?Generator 和异步迭代器有什么应用场景?
答案
迭代器模式(Iterator Pattern)是 GoF 23 种经典设计模式之一,它提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示。JavaScript 通过 Iterator Protocol 和 Iterable Protocol 将这一模式内建到语言层面,使得 for...of、展开运算符、解构赋值等语法特性都依赖于统一的迭代协议。
更多关于 Symbol 和迭代器的基础知识,可参考 Symbol 与迭代器。
GoF 经典定义与核心角色
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
在经典面向对象设计中,迭代器模式涉及两个核心角色:
| 角色 | 职责 | JS 中的对应 |
|---|---|---|
| Iterator(迭代器) | 定义访问和遍历元素的接口 | { next(): { value, done } } |
| Aggregate(聚合) | 定义创建迭代器的接口 | { [Symbol.iterator](): Iterator } |
JavaScript 迭代器协议(Iterator Protocol)
一个对象只要实现了 next() 方法,并且该方法返回 { value, done } 形式的结果,就满足迭代器协议。
// 迭代器接口定义
interface IteratorResult<T> {
value: T;
done: boolean;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>; // 可选:提前终止
throw?(error?: any): IteratorResult<T>; // 可选:抛出错误
}
// 手写一个最简单的迭代器
function createCountIterator(start: number, end: number): Iterator<number> {
let current = start;
return {
next(): IteratorResult<number> {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined as any, done: true };
},
};
}
const iter = createCountIterator(1, 3);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }
done: true 时的 value 通常为 undefined,但它也可以有值(如 Generator 的 return 返回值)。消费迭代器的语法(如 for...of)会忽略 done: true 时的 value。
可迭代协议(Iterable Protocol)
一个对象实现了 [Symbol.iterator]() 方法,返回一个迭代器,就满足可迭代协议。满足可迭代协议的对象可以被 for...of、展开运算符、解构赋值等语法消费。
// 可迭代对象接口
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
// 创建一个可迭代的范围对象
class Range implements Iterable<number> {
constructor(
private start: number,
private end: number,
private 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 as any, done: true };
},
};
}
}
const range = new Range(1, 5);
// for...of 会自动调用 [Symbol.iterator]()
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// 展开运算符
console.log([...range]); // [1, 2, 3, 4, 5]
// 解构赋值
const [first, second, ...rest] = range;
console.log(first, second, rest); // 1 2 [3, 4, 5]
// Array.from
console.log(Array.from(range)); // [1, 2, 3, 4, 5]
内置可迭代对象
JavaScript 中以下内置对象默认实现了可迭代协议:
| 内置对象 | 迭代内容 | 示例 |
|---|---|---|
Array | 数组元素 | for (const item of [1, 2, 3]) |
String | Unicode 字符 | for (const char of 'hello') |
Map | [key, value] 键值对 | for (const [k, v] of map) |
Set | 集合元素 | for (const item of set) |
TypedArray | 类型数组元素 | for (const byte of uint8Array) |
arguments | 函数参数 | for (const arg of arguments) |
NodeList | DOM 节点 | for (const node of nodeList) |
普通对象 {} 默认不是可迭代的,for...of 无法直接遍历普通对象。如果需要遍历对象的键值对,可以使用 Object.entries()、Object.keys() 或 Object.values(),它们返回的是数组(数组是可迭代的)。
// Map 的迭代
const map = new Map<string, number>([
['a', 1],
['b', 2],
]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`); // a: 1, b: 2
}
// String 支持 Unicode 迭代(正确处理代理对)
const emoji = '👨👩👧';
console.log([...emoji]); // 正确拆分 emoji
// 普通对象需要通过 Object.entries() 转换
const obj = { x: 1, y: 2 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`); // x: 1, y: 2
}
for...of 原理与 for...in 的区别
for...of 的本质是调用可迭代对象的 [Symbol.iterator]() 方法获取迭代器,然后不断调用 next() 直到 done 为 true。
// for...of 语法糖的等价实现
const arr = [10, 20, 30];
// 这段 for...of:
for (const item of arr) {
console.log(item);
}
// 等价于:
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const item = result.value;
console.log(item);
result = iterator.next();
}
for...of vs for...in 对比:
| 特性 | for...of | for...in |
|---|---|---|
| 遍历内容 | 可迭代对象的值 | 对象的可枚举属性键(包括原型链) |
| 适用类型 | 实现了 [Symbol.iterator] 的对象 | 任何对象 |
| 数组遍历 | 遍历元素值 | 遍历索引字符串("0", "1", ...) |
| 原型链 | 不涉及 | 会遍历原型链上的可枚举属性 |
| 顺序保证 | 按迭代器定义的顺序 | 不保证顺序(实际有规则但规范不强制) |
| 可中断 | break / return 触发 iterator.return() | break 即可 |
const arr = [10, 20, 30];
// for...of 遍历值
for (const value of arr) {
console.log(value); // 10, 20, 30
}
// for...in 遍历键(索引字符串)
for (const key in arr) {
console.log(key); // "0", "1", "2"
}
// ⚠️ for...in 会遍历原型链上的可枚举属性
(Array.prototype as any).customMethod = function () {};
for (const key in arr) {
console.log(key); // "0", "1", "2", "customMethod"
}
永远不要用 for...in 遍历数组!它会遍历原型链属性,且索引是字符串而非数字。遍历数组请使用 for...of、forEach 或传统 for 循环。
自定义迭代器实现
链表迭代器
class ListNode<T> {
constructor(
public value: T,
public next: ListNode<T> | null = null
) {}
}
class LinkedList<T> implements Iterable<T> {
head: ListNode<T> | null = null;
append(value: T): void {
const node = new ListNode(value);
if (!this.head) {
this.head = node;
return;
}
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
}
[Symbol.iterator](): Iterator<T> {
let current = this.head;
return {
next(): IteratorResult<T> {
if (current) {
const value = current.value;
current = current.next;
return { value, done: false };
}
return { value: undefined as any, done: true };
},
};
}
}
const list = new LinkedList<number>();
list.append(1);
list.append(2);
list.append(3);
for (const value of list) {
console.log(value); // 1, 2, 3
}
console.log([...list]); // [1, 2, 3]
树形结构迭代器(深度优先)
interface TreeNode<T> {
value: T;
children: TreeNode<T>[];
}
class Tree<T> implements Iterable<T> {
constructor(private root: TreeNode<T>) {}
// 深度优先遍历(前序)
*[Symbol.iterator](): Generator<T> {
function* traverse(node: TreeNode<T>): Generator<T> {
yield node.value;
for (const child of node.children) {
yield* traverse(child);
}
}
yield* traverse(this.root);
}
}
const tree = new Tree<string>({
value: 'root',
children: [
{
value: 'A',
children: [
{ value: 'A1', children: [] },
{ value: 'A2', children: [] },
],
},
{
value: 'B',
children: [{ value: 'B1', children: [] }],
},
],
});
console.log([...tree]); // ['root', 'A', 'A1', 'A2', 'B', 'B1']
无限序列迭代器
// 斐波那契无限序列
function* fibonacci(): Generator<number> {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 取前 10 个斐波那契数
function take<T>(iterable: Iterable<T>, count: number): T[] {
const result: T[] = [];
for (const item of iterable) {
if (result.length >= count) break;
result.push(item);
}
return result;
}
console.log(take(fibonacci(), 10));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
无限序列是迭代器模式的独特优势——惰性求值。数据只有在被消费时才会计算,避免了一次性生成所有数据的内存开销。
Generator 函数
Generator 函数是创建迭代器的语法糖,使用 function* 声明,通过 yield 暂停和恢复执行。Generator 函数返回的对象同时满足迭代器协议和可迭代协议。
// 基础 Generator
function* numberGenerator(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
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 }
// Generator 同时是可迭代对象
for (const num of numberGenerator()) {
console.log(num); // 1, 2, 3
}
yield* 委托
yield* 可以将迭代委托给另一个可迭代对象或 Generator:
function* innerGenerator(): Generator<string> {
yield 'inner-1';
yield 'inner-2';
}
function* outerGenerator(): Generator<string | number> {
yield 'start';
yield* innerGenerator(); // 委托给另一个 Generator
yield* [100, 200]; // 委托给数组(也是可迭代对象)
yield 'end';
}
console.log([...outerGenerator()]);
// ['start', 'inner-1', 'inner-2', 100, 200, 'end']
Generator 的双向通信
function* calculator(): Generator<number, string, number> {
const a = yield 0; // 暂停,等待外部传入 a
const b = yield a; // 暂停,等待外部传入 b
return `${a} + ${b} = ${a + b}`;
}
const calc = calculator();
console.log(calc.next()); // { value: 0, done: false }
console.log(calc.next(10)); // { value: 10, done: false } — a = 10
console.log(calc.next(20)); // { value: '10 + 20 = 30', done: true } — b = 20
第一次调用 next() 时传入的参数会被忽略,因为此时没有 yield 表达式在等待接收值。从第二次 next(value) 开始,value 会成为上一个 yield 表达式的返回值。
异步迭代器
ES2018 引入了异步迭代协议,用于处理异步数据流。异步迭代器的 next() 返回 Promise<IteratorResult>。
// 异步可迭代接口
interface AsyncIterable<T> {
[Symbol.asyncIterator](): AsyncIterator<T>;
}
interface AsyncIterator<T> {
next(): Promise<IteratorResult<T>>;
}
// 模拟分页 API 请求
async function* fetchPages<T>(
url: string,
maxPages: number = Infinity
): AsyncGenerator<T[]> {
let page = 1;
while (page <= maxPages) {
const response = await fetch(`${url}?page=${page}`);
const data: T[] = await response.json();
if (data.length === 0) break; // 没有更多数据
yield data;
page++;
}
}
// 使用 for await...of 消费异步迭代器
async function loadAllUsers(): Promise<void> {
for await (const users of fetchPages<User>('/api/users', 10)) {
console.log(`加载了 ${users.length} 个用户`);
renderUsers(users);
}
}
流式数据处理
// 异步 Generator 处理 SSE(Server-Sent Events)流
async function* parseSSEStream(
response: Response
): AsyncGenerator<string> {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop()!; // 保留未完成的行
for (const line of lines) {
if (line.startsWith('data: ')) {
yield line.slice(6);
}
}
}
} finally {
reader.releaseLock();
}
}
// 消费流式 AI 响应
async function streamChat(prompt: string): Promise<void> {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt }),
});
for await (const chunk of parseSSEStream(response)) {
appendToUI(chunk);
}
}
异步迭代器工具函数
// 异步版 map
async function* asyncMap<T, U>(
source: AsyncIterable<T>,
fn: (item: T) => U | Promise<U>
): AsyncGenerator<U> {
for await (const item of source) {
yield await fn(item);
}
}
// 异步版 filter
async function* asyncFilter<T>(
source: AsyncIterable<T>,
predicate: (item: T) => boolean | Promise<boolean>
): AsyncGenerator<T> {
for await (const item of source) {
if (await predicate(item)) {
yield item;
}
}
}
// 异步版 take
async function* asyncTake<T>(
source: AsyncIterable<T>,
count: number
): AsyncGenerator<T> {
let taken = 0;
for await (const item of source) {
if (taken >= count) break;
yield item;
taken++;
}
}
// 组合使用
async function processStream(): Promise<void> {
const pages = fetchPages<User>('/api/users');
const activeUsers = asyncFilter(pages, (users) =>
users.some((u) => u.isActive)
);
const firstThree = asyncTake(activeUsers, 3);
for await (const users of firstThree) {
console.log(users);
}
}
前端实际应用场景
DOM 树遍历
// 使用迭代器遍历 DOM 树
function* walkDOM(root: Element): Generator<Element> {
yield root;
for (const child of root.children) {
yield* walkDOM(child); // 递归委托
}
}
// 查找所有包含特定属性的元素
function findElementsByAttribute(
root: Element,
attr: string
): Element[] {
const result: Element[] = [];
for (const el of walkDOM(root)) {
if (el.hasAttribute(attr)) {
result.push(el);
}
}
return result;
}
const elements = findElementsByAttribute(
document.body,
'data-tracking'
);
分页数据懒加载
// 分页数据的惰性加载迭代器
class PaginatedData<T> implements AsyncIterable<T> {
constructor(
private fetchFn: (page: number, size: number) => Promise<T[]>,
private pageSize: number = 20
) {}
async *[Symbol.asyncIterator](): AsyncGenerator<T> {
let page = 1;
let hasMore = true;
while (hasMore) {
const items = await this.fetchFn(page, this.pageSize);
for (const item of items) {
yield item; // 逐个 yield,而非整页
}
hasMore = items.length === this.pageSize;
page++;
}
}
}
// 使用
const users = new PaginatedData<User>(
(page, size) => fetch(`/api/users?page=${page}&size=${size}`)
.then((res) => res.json())
);
for await (const user of users) {
renderUserCard(user);
// 可以随时 break 停止加载
if (shouldStop()) break;
}
状态历史遍历(撤销/重做)
class StateHistory<T> implements Iterable<T> {
private history: T[] = [];
private currentIndex: number = -1;
push(state: T): void {
// 如果当前不在最新位置,截断后续状态
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(state);
this.currentIndex++;
}
undo(): T | undefined {
if (this.currentIndex > 0) {
return this.history[--this.currentIndex];
}
}
redo(): T | undefined {
if (this.currentIndex < this.history.length - 1) {
return this.history[++this.currentIndex];
}
}
// 正向迭代所有历史状态
[Symbol.iterator](): Iterator<T> {
let index = 0;
const history = this.history;
return {
next(): IteratorResult<T> {
if (index < history.length) {
return { value: history[index++], done: false };
}
return { value: undefined as any, done: true };
},
};
}
// 反向迭代(从最新到最旧)
*reversed(): Generator<T> {
for (let i = this.history.length - 1; i >= 0; i--) {
yield this.history[i];
}
}
}
const history = new StateHistory<string>();
history.push('state-1');
history.push('state-2');
history.push('state-3');
// 正向遍历
for (const state of history) {
console.log(state); // state-1, state-2, state-3
}
// 反向遍历
for (const state of history.reversed()) {
console.log(state); // state-3, state-2, state-1
}
迭代器模式的优缺点
| 维度 | 说明 |
|---|---|
| 优点 | 统一遍历接口,解耦集合与遍历逻辑 |
| 支持惰性求值,节省内存 | |
| 可组合,多个迭代器可串联(map/filter/take) | |
| 支持无限序列 | |
| 缺点 | 单向遍历,无法直接回退(需额外实现) |
| 简单场景下可能过度设计 | |
| Generator 的调试体验相对复杂 |
常见面试问题
Q1: 什么是迭代器协议和可迭代协议?它们之间有什么关系?
答案:
迭代器协议(Iterator Protocol):一个对象实现了 next() 方法,该方法返回 { value, done } 形式的结果,就满足迭代器协议。done 为 false 表示还有值,为 true 表示迭代结束。
可迭代协议(Iterable Protocol):一个对象实现了 [Symbol.iterator]() 方法,该方法返回一个满足迭代器协议的对象,就满足可迭代协议。
两者的关系:可迭代协议是"工厂",每次调用 [Symbol.iterator]() 创建一个新的迭代器;迭代器协议是"游标",负责实际的遍历逻辑。
// 可迭代对象(实现 Symbol.iterator)
const iterable = {
[Symbol.iterator](): Iterator<number> {
let i = 0;
// 返回迭代器(实现 next)
return {
next(): IteratorResult<number> {
return i < 3
? { value: ++i, done: false }
: { value: undefined as any, done: true };
},
};
},
};
// 每次 for...of 都会创建新的迭代器
for (const n of iterable) console.log(n); // 1, 2, 3
for (const n of iterable) console.log(n); // 1, 2, 3(重新开始)
Q2: for...of 和 for...in 的区别是什么?
答案:
| 对比项 | for...of | for...in |
|---|---|---|
| 遍历目标 | 值(可迭代对象的元素) | 键(对象的可枚举属性名) |
| 适用对象 | 实现了 [Symbol.iterator] 的对象 | 任何对象 |
| 原型链 | 不涉及原型链 | 会遍历原型链上的可枚举属性 |
| 数组表现 | 遍历元素值 | 遍历索引字符串 |
| 普通对象 | 默认不可用(需自定义迭代器) | 可直接使用 |
const arr = ['a', 'b', 'c'];
for (const value of arr) console.log(value); // 'a', 'b', 'c'
for (const key in arr) console.log(key); // '0', '1', '2'
// for...in 的陷阱:会遍历原型链
(Array.prototype as any).foo = 'bar';
for (const key in arr) console.log(key); // '0', '1', '2', 'foo' ❌
选择原则:遍历数组/Map/Set 用 for...of,遍历对象属性用 Object.keys() + for...of,尽量避免 for...in。
Q3: 如何让一个自定义对象支持 for...of?
答案:
实现 [Symbol.iterator]() 方法即可。有两种常见方式:
// 方式一:手写 next() 方法
class TodoList {
private items: string[] = [];
add(item: string): void {
this.items.push(item);
}
[Symbol.iterator](): Iterator<string> {
let index = 0;
const items = this.items;
return {
next(): IteratorResult<string> {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { value: undefined as any, done: true };
},
};
}
}
// 方式二:使用 Generator(更简洁)
class TodoListV2 {
private items: string[] = [];
add(item: string): void {
this.items.push(item);
}
*[Symbol.iterator](): Generator<string> {
for (const item of this.items) {
yield item;
}
// 或简写为: yield* this.items;
}
}
const todos = new TodoListV2();
todos.add('学习迭代器');
todos.add('刷面试题');
for (const todo of todos) {
console.log(todo);
}
推荐使用 Generator 方式,代码更简洁,维护成本更低。
Q4: Generator 和 Iterator 是什么关系?
答案:
Generator 是创建迭代器的语法糖。调用 Generator 函数返回的对象同时满足迭代器协议和可迭代协议:
function* gen(): Generator<number> {
yield 1;
yield 2;
}
const g = gen();
// ✅ 满足迭代器协议(有 next 方法)
console.log(g.next()); // { value: 1, done: false }
// ✅ 满足可迭代协议(有 Symbol.iterator)
console.log(g[Symbol.iterator]() === g); // true(返回自身)
关键区别:
| 特性 | 手写 Iterator | Generator |
|---|---|---|
| 语法复杂度 | 需手动维护状态 | yield 自动管理状态 |
| 双向通信 | 不支持 | next(value) 传值、throw(error) 抛错 |
| 嵌套迭代 | 手动递归 | yield* 委托 |
return() | 需手动实现 | 自动支持(执行 finally 块) |
Q5: 异步迭代器的应用场景有哪些?
答案:
异步迭代器(Symbol.asyncIterator + for await...of)适用于逐步产生的异步数据流场景:
- 分页 API 请求:逐页加载数据,每页是一个异步操作
- 流式响应:SSE、ReadableStream、AI 流式回复
- 文件逐行读取:Node.js 中大文件处理
- WebSocket 消息流:将消息封装为异步可迭代
- 数据库游标:大量数据的批次读取
// 实际应用:将 WebSocket 包装为异步迭代器
function createWSIterable(url: string): AsyncIterable<string> {
return {
[Symbol.asyncIterator]() {
const ws = new WebSocket(url);
const messageQueue: string[] = [];
let resolve: (() => void) | null = null;
let done = false;
ws.onmessage = (e) => {
messageQueue.push(e.data);
resolve?.();
};
ws.onclose = () => {
done = true;
resolve?.();
};
return {
async next(): Promise<IteratorResult<string>> {
while (messageQueue.length === 0 && !done) {
await new Promise<void>((r) => (resolve = r));
}
if (messageQueue.length > 0) {
return { value: messageQueue.shift()!, done: false };
}
return { value: undefined as any, done: true };
},
async return(): Promise<IteratorResult<string>> {
ws.close();
return { value: undefined as any, done: true };
},
};
},
};
}
// 使用
for await (const message of createWSIterable('wss://example.com')) {
handleMessage(message);
}
Q6: 展开运算符 ... 和解构赋值的底层是如何调用迭代器的?
答案:
展开运算符和数组解构赋值都会在底层调用对象的 [Symbol.iterator]() 方法:
function* gen(): Generator<number> {
console.log('yield 1');
yield 1;
console.log('yield 2');
yield 2;
console.log('yield 3');
yield 3;
}
// 展开运算符:消费所有值
const arr = [...gen()];
// 输出: yield 1, yield 2, yield 3
// arr = [1, 2, 3]
// 解构赋值:惰性消费
const [first, second] = gen();
// 输出: yield 1, yield 2(只消费了两个!)
// first = 1, second = 2
解构赋值是惰性的——只消费需要的元素个数。这意味着对无限序列使用解构是安全的:const [a, b] = fibonacci() 只会计算前两个值。
其他底层调用迭代器的语法:
Array.from(iterable)new Map(iterable)/new Set(iterable)Promise.all(iterable)/Promise.race(iterable)yield* iterable
Q7: 迭代器的 return() 和 throw() 方法有什么作用?
答案:
除了必须的 next() 方法,迭代器还可以实现两个可选方法:
| 方法 | 触发时机 | 作用 |
|---|---|---|
return(value?) | break、return、异常导致提前退出 for...of 时 | 释放资源、清理状态 |
throw(error?) | 外部向 Generator 内部抛错 | 在 yield 处触发异常 |
function* resourceGenerator(): Generator<number> {
console.log('获取资源');
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('释放资源'); // break 时也会执行
}
}
// break 触发 return(),进而执行 finally
for (const value of resourceGenerator()) {
console.log(value);
if (value === 1) break;
}
// 输出: 获取资源 → 1 → 释放资源
// throw() 的使用
function* errorHandlingGen(): Generator<number> {
try {
yield 1;
yield 2;
} catch (e) {
console.log('捕获错误:', (e as Error).message);
yield -1; // 错误恢复
}
}
const g = errorHandlingGen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw(new Error('oops'))); // 捕获错误: oops → { value: -1, done: false }
Q8: 如何实现一个支持链式调用的惰性迭代工具库?
答案:
借鉴函数式编程的管道思想,将 map、filter、take 等操作实现为返回新迭代器的函数,实现惰性链式调用:
class LazyIterator<T> implements Iterable<T> {
constructor(private source: Iterable<T>) {}
[Symbol.iterator](): Iterator<T> {
return this.source[Symbol.iterator]();
}
map<U>(fn: (item: T) => U): LazyIterator<U> {
const source = this.source;
return new LazyIterator({
*[Symbol.iterator]() {
for (const item of source) {
yield fn(item);
}
},
});
}
filter(predicate: (item: T) => boolean): LazyIterator<T> {
const source = this.source;
return new LazyIterator({
*[Symbol.iterator]() {
for (const item of source) {
if (predicate(item)) yield item;
}
},
});
}
take(count: number): LazyIterator<T> {
const source = this.source;
return new LazyIterator({
*[Symbol.iterator]() {
let taken = 0;
for (const item of source) {
if (taken >= count) break;
yield item;
taken++;
}
},
});
}
// 终结操作:收集为数组
toArray(): T[] {
return [...this];
}
// 终结操作:归约
reduce<U>(fn: (acc: U, item: T) => U, initial: U): U {
let acc = initial;
for (const item of this) {
acc = fn(acc, item);
}
return acc;
}
}
// 辅助函数
function lazy<T>(source: Iterable<T>): LazyIterator<T> {
return new LazyIterator(source);
}
// 使用示例 —— 所有操作都是惰性的,只有 toArray() 时才真正执行
const result = lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.filter((n) => n % 2 === 0) // 惰性:不会立即执行
.map((n) => n * n) // 惰性
.take(3) // 惰性
.toArray(); // 终结操作,触发计算
console.log(result); // [4, 16, 36]
这种惰性链式迭代模式在处理大数据集时非常高效——take(3) 意味着最多只会遍历到第 6 个元素(3 个偶数),而不是遍历整个数组。这就是惰性求值相比急切求值(如 Array.prototype.filter + map)的核心优势。
Q9: 迭代器模式在 React/Vue 等前端框架中有哪些实际应用?
答案:
-
React 中
children的迭代:React.Children提供了遍历子元素的迭代工具 -
状态管理中的时间旅行:Redux DevTools 的撤销/重做本质上是状态历史的迭代
-
虚拟列表数据源:将大数据集包装为惰性迭代器,按需渲染
-
路由匹配:React Router 内部使用迭代器模式匹配路由配置
-
异步数据加载:使用异步迭代器实现数据流
// 实际案例:React 中用异步迭代器实现无限滚动 Hook
function useAsyncIterator<T>(
iteratorFactory: () => AsyncIterableIterator<T>
): { items: T[]; loadMore: () => void; loading: boolean } {
const [items, setItems] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const iteratorRef = useRef<AsyncIterableIterator<T>>();
useEffect(() => {
iteratorRef.current = iteratorFactory();
}, []);
const loadMore = useCallback(async () => {
if (!iteratorRef.current || loading) return;
setLoading(true);
const { value, done } = await iteratorRef.current.next();
if (!done && value !== undefined) {
setItems((prev) => [...prev, value]);
}
setLoading(false);
}, [loading]);
return { items, loadMore, loading };
}
Q10: Iterator 与 Array 的高阶方法(map/filter/reduce)有什么区别?如何选择?
答案:
| 对比项 | Array 高阶方法 | Iterator |
|---|---|---|
| 求值策略 | 急切求值:立即处理所有元素 | 惰性求值:按需计算 |
| 内存占用 | 每步生成新数组 | 不生成中间数组 |
| 无限序列 | 不支持 | 支持 |
| 链式操作 | 每步都完整遍历 | 单次遍历完成所有操作 |
| 可读性 | 更直观 | 需要理解迭代器概念 |
| 浏览器支持 | 全支持 | 需 ES6+(Generator 需 ES2015+) |
选择原则:
- 数据量小、操作简单 → Array 方法(可读性优先)
- 数据量大、多步操作 → Iterator(减少中间数组的内存开销)
- 无限序列、流式数据 → Iterator(唯一选择)
- 异步数据流 → 异步迭代器
// 数组方法:三次遍历,创建两个中间数组
const result1 = [1, 2, 3, 4, 5]
.filter((n) => n % 2 === 0) // 遍历 5 个,生成 [2, 4]
.map((n) => n * 10) // 遍历 2 个,生成 [20, 40]
.slice(0, 1); // [20]
// Iterator:单次遍历,零中间数组
const result2 = lazy([1, 2, 3, 4, 5])
.filter((n) => n % 2 === 0)
.map((n) => n * 10)
.take(1)
.toArray(); // 只遍历到元素 2 就停了!结果 [20]
TC39 提案 Iterator Helpers 正在推进中,未来 JavaScript 内置迭代器将原生支持 .map()、.filter()、.take() 等方法,届时惰性迭代将成为一等公民。