自定义迭代器实现可遍历类实例方法

作者:技术拾光

时间:2026-05-27

浏览:654

如何让自定义类实例支持 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 迭代生态,编写出更通用、优雅的代码。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章