自定义迭代器实现可遍历类实例方法
如何让自定义类实例支持 for...of 循环、扩展运算符或解构赋值?实现迭代器协议是关键。只需为你的类定义一个 [Symbol.iterator] 方法,并让它返回一个符合规范的对象即可。 该对象必须包含一个 next() 方法,每次调用返回一个形如 { value, done } 的结果。当遍历
如何让自定义类实例支持 for...of 循环、扩展运算符或解构赋值?实现迭代器协议是关键。只需为你的类定义一个 [Symbol.iterator] 方法,并让它返回一个符合规范的对象即可。

该对象必须包含一个 next() 方法,每次调用返回一个形如 { value, done } 的结果。当遍历完成时,done 变为 true。下面通过具体场景来详细说明。
自定义迭代器基础实现
最常见的场景是让实例按顺序输出其内部数据。例如,一个封装了数组的集合类。通过在类的原型上定义 [Symbol.iterator] 方法,并利用闭包追踪遍历位置,可以轻松实现。
以下是一个遍历内部数组的示例:
class Collection {
constructor(items = []) {
this.items = items;
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
}
const coll = new Collection(['a', 'b', 'c']);
for (const item of coll) {
console.log(item); // 依次输出 'a', 'b', 'c'
}
console.log([...coll]); // 输出 ['a', 'b', 'c']
支持多种遍历模式的设计
对于更复杂的数据结构(如模拟 Map),可能需要支持遍历键、值或键值对。此时,可将 [Symbol.iterator] 设为默认模式(例如遍历值),并通过其他方法提供不同选择。
设计要点包括:
- 避免在
[Symbol.iterator]中固化逻辑,可委托给内部方法。 - 通过
.keys()、.values()、.entries()等方法暴露不同的迭代器。 - 所有方法返回的迭代器都必须遵守相同协议。
以下示例模拟了 Map 接口,并使用了更简洁的生成器函数:
class MyMap {
constructor(entries = []) {
this._data = new Map(entries);
}
[Symbol.iterator]() {
return this.values(); // 默认遍历值
}
*keys() {
for (const key of this._data.keys()) {
yield key;
}
}
*values() {
for (const value of this._data.values()) {
yield value;
}
}
*entries() {
for (const entry of this._data.entries()) {
yield entry;
}
}
}
代码中的星号(*)表示生成器函数。它自动返回迭代器并管理内部状态,比手动编写 next() 方法更简洁安全,是推荐的首选方式。
实现自定义迭代器的注意事项
实现自定义迭代器时,需注意以下常见问题:
- 返回迭代器对象而非数据序列:
[Symbol.iterator]()必须返回带next()方法的对象。 - 迭代结束后的行为:即使
done为true,后续调用next()也应始终返回{ value: undefined, done: true }。 - 状态隔离:若迭代器依赖实例的可变属性,遍历过程中属性被修改可能导致意外结果。必要时可在迭代器内部对数据做快照。
- 优先使用生成器:生成器函数(
function*或*method())能简化实现,并原生支持yield*委托等高级特性。
异步迭代器的实现方法
当数据源为异步时(如从网络 API 分页获取),需使用异步迭代器协议,对应方法为 [Symbol.asyncIterator]。
异步迭代器的 next() 方法返回一个 Promise,其解析值为 { value, done } 形式。结合 for await...of 循环,可优雅处理异步数据流。
以下是一个模拟异步分页获取数据的示例:
class AsyncCollection {
constructor(fetcher) {
this.fetcher = fetcher; // 函数:(page) => Promise
}
async *[Symbol.asyncIterator]() {
let page = 1;
while (true) {
const items = await this.fetcher(page);
if (items.length === 0) break;
for (const item of items) {
yield item; // 异步生成器自动将值包装在 Promise 中
}
page++;
}
}
}
// 使用方式:
// for await (const item of asyncColl) {
// console.log(item);
// }
总结而言,无论是同步还是异步遍历,核心都在于遵循 ECMAScript 规范定义的迭代器协议。掌握 [Symbol.iterator] 和 [Symbol.asyncIterator] 的实现,能让自定义数据结构完美融入 JavaScript 迭代生态,编写出更通用、优雅的代码。
