讲解
图书管理员带读者找书,读者只需要跟着说「下一本」——书是按书名排的还是按索书号排的、在几楼哪个架,读者完全不关心。迭代器模式就是这位管理员:提供一种方法顺序访问一个聚合对象的各个元素,而又不暴露该对象的内部表示。
在 GoF 年代,迭代器需要手写一堆 hasNext()/next() 类;而在现代 JavaScript/TypeScript 里,这个模式已经被语言特性「收编」了:任何实现了 Symbol.iterator 方法的对象都是可迭代对象(Iterable),直接享受 for..of、展开运算符、解构、Array.from 等一整套语言设施。数组、Map、Set、字符串都实现了它,你自己写的集合类也应该实现它。
比手写迭代器类更进一步的语法糖是生成器函数(function*):function* 里的每个 yield 产出序列的一个值,函数天然就是一个迭代器工厂。原本要维护「游标状态」的样板代码,被 yield 的暂停/恢复语义接管,定义一个数列迭代器只要三四行。
什么时候还需要自己写?当你的集合不是数组时——树的中序遍历、分页 API 的自动翻页、无限数列(斐波那契)、按规则跳过的过滤序列。给这些自定义结构实现 Symbol.iterator,它们就立刻能和数组一样被 for..of 消费,调用方代码统一而优雅。
示例
自定义 Iterable(区间迭代器)+ 生成器函数(斐波那契数列),两者都能直接用 for..of 和展开运算符:
import assert from 'node:assert/strict';
// 实现 Symbol.iterator,就接入了 for..of、展开、解构等全部语言设施
class Range implements Iterable<number> {
constructor(
private readonly from: number,
private readonly to: number,
private readonly step: number = 1,
) {}
*[Symbol.iterator](): Iterator<number> {
for (let value = this.from; value <= this.to; value += this.step) {
yield value;
}
}
}
const evens = [...new Range(2, 10, 2)];
assert.deepEqual(evens, [2, 4, 6, 8, 10]);
// 生成器函数:最轻量的迭代器写法,状态由 yield 自动保管
function* fibonacci(limit: number): Generator<number> {
let a = 0;
let b = 1;
while (a <= limit) {
yield a;
const next = a + b;
a = b;
b = next;
}
}
const fibs: number[] = [];
for (const n of fibonacci(20)) {
fibs.push(n);
}
assert.deepEqual(fibs, [0, 1, 1, 2, 3, 5, 8, 13]);
console.log('偶数:' + evens.join(', '));
console.log('斐波那契:' + fibs.join(', '));
注意 Range 里的 *Symbol.iterator 是「生成器方法」语法——连手写 next() 返回 { value, done } 都省了。生成器还有一个隐藏优点:惰性。fibonacci 不写 limit 就是无限数列,值只在被消费时才计算,这在处理大数据流时是内存上的关键特性。
常见坑
- 迭代过程中修改集合:for..of 数组时往里 push,多数语言直接抛错或行为未定义;要边遍历边改,先迭代一个快照副本。
- 自己写 hasNext/next 而不用 Symbol.iterator:等于把集合关在了语言设施门外,for..of、展开、Array.from 全都用不上。
- 误以为迭代器能重来:生成器是一次性的,消费完就耗尽;需要多次遍历就每次调用工厂函数拿新的迭代器。
- 无限数列不设出口:for..of 一个无限生成器而没有 break,就是死循环;无限序列的消费方必须自己决定何时停。
- 迭代器里做 IO 却同步签名:逐行读大文件、翻页拉 API 应该用异步迭代器(Symbol.asyncIterator + for await..of),同步迭代器塞 Promise 只会让消费方踩雷。
小结
迭代器模式在 TS 里已内化为 Symbol.iterator 协议:实现它即接入 for..of 生态,生成器函数把状态维护降到零成本,惰性是它的隐藏福利。下一章:对象多对多通信乱成一锅粥时,请出中介者。