每当你在数组上链式调用 .filter().map() 时,JavaScript 都会创建两个新数组。对于小列表,这几乎察觉不到。但对于大型数据集、按需产生值的生成器,或是你只需要前几个结果的流,你已经物化了成千上万个立即丢弃的元素。

迭代器辅助方法是惰性替代方案。它们已原生发布于 Chrome 122、Firefox 131 和 Safari 18.2——无需库、无需 polyfill、无需构建步骤。

“惰性”在实践中的含义

数组方法是急切的:它们立即执行并返回一个全新的、完全填充的数组。

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = numbers
  .filter(n => n % 2 === 0)  // → 分配 [2, 4, 6, 8, 10]
  .map(n => n * n)            // → 分配 [4, 16, 36, 64, 100]
  .slice(0, 3);               // → 分配 [4, 16, 36]

Enter fullscreen mode Exit fullscreen mode

创建了三个数组并被丢弃。只有最后一个保留下来。

迭代器辅助方法是惰性的:它们构建一个管道描述。在你请求之前,不会处理任何元素。

const result = Iterator.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(3)
  .toArray();

// → [4, 16, 36]

Enter fullscreen mode Exit fullscreen mode

相同的输出。没有中间数组。管道一次处理一个元素:1 经过 .filter(),失败并被丢弃。2 通过 .filter(),经过 .map(),计入 .take(3)。在三个通过的元素之后循环停止——剩余的数字从未被访问。

Iterator.from() 及其返回内容

Iterator.from() 将任何可迭代对象——数组、Set、Map、字符串、生成器返回值——包装在一个附加了迭代器辅助方法的对象中。

const set = new Set([1, 1, 2, 2, 3, 3, 4, 5]);
const result = Iterator.from(set)
  .filter(n => n % 2 !== 0)
  .toArray();

// → [1, 3, 5]

Enter fullscreen mode Exit fullscreen mode

生成器是自然的契合。生成器已经按需产生值;迭代器辅助方法让你在它们之上构建管道,而无需创建中间数组。

function* naturals() {
  let n = 0;
  while (true) yield n++;
}

const firstFiveSquaredEvens = Iterator.from(naturals())
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(5)
  .toArray();

// → [0, 4, 16, 36, 64]

Enter fullscreen mode Exit fullscreen mode

你无法对无限生成器使用数组方法——.filter() 在数组上需要到达末尾才能返回任何内容。

完整的方法集

迭代器辅助方法提供了十个方法。第一组构建管道(惰性);第二组运行它(终端):

Lazy:     .map(fn)  .filter(fn)  .take(n)  .drop(n)  .flatMap(fn)
Terminal: .toArray()  .reduce(fn, init)  .find(fn)  .some(fn)  .every(fn)  .forEach(fn)

Enter fullscreen mode Exit fullscreen mode

调用惰性方法返回一个新的迭代器——什么都不会运行。调用终端方法会通过整个链拉取值,直到它得到所需的内容,然后停止。

// .find() 停止在第一个匹配处;其余数据不会被触及
const firstLongWord = Iterator.from(wordList)
  .filter(w => w.startsWith('pre'))
  .find(w => w.length > 10);

Enter fullscreen mode Exit fullscreen mode

实际示例:处理大型列表

你正在渲染一个包含 10,000 条记录的可过滤列表。用户输入了搜索词并选择了类别。视图显示前 50 个匹配项。

使用数组:

const visible = allRecords
  .filter(r => r.category === selectedCategory)  // 遍历所有 10,000 条
  .filter(r => r.name.includes(query))           // 遍历过滤后的集合
  .slice(0, 50);                                 // 最后限制

Enter fullscreen mode Exit fullscreen mode

使用迭代器辅助方法:

const visible = Iterator.from(allRecords)
  .filter(r => r.category === selectedCategory)
  .filter(r => r.name.includes(query))
  .take(50)
  .toArray();

Enter fullscreen mode Exit fullscreen mode

迭代器版本在获得 50 个匹配项后立即停止。如果这 50 个出现在前 200 条记录中,剩余的 9,800 条记录将永远不会被检查。而使用数组版本,无论如何,每个记录都会在第一次过滤过程中被访问。

何时继续使用数组方法

迭代器辅助方法不是万能的替代品:

  • 你需要随机访问:数组允许你按位置索引。迭代器只能向前移动一次。
  • 你需要多次遍历数据:你只能消费一次迭代器。数组是可重用的。
  • 你需要 .sort():排序需要在内存中保存完整数据集。没有惰性排序。
  • 列表很小:对于十个项目,Iterator.from() 的开销可以忽略不计。使用任何阅读起来更清晰的方式。

最明确的信号:如果你在一个大型或可能无限的源上链式调用数组方法并丢弃中间结果,这就是迭代器辅助方法的用例。

TypeScript 支持

TypeScript 5.6 添加了 Iterator 全局类型和方法签名。如果你的 tsconfig.json 目标是 ES2024 或更高版本,它们会自动可用:

function* ids(): Generator<number> {
  let id = 0;
  while (true) yield id++;
}

const first10: number[] = Iterator.from(ids()).take(10).toArray();

Enter fullscreen mode Exit fullscreen mode

对于更早的 lib 目标,你可能需要显式地将 "ES2024" 添加到 lib 数组中,或者如果你正在使用 polyfill,则使用 @types/core-js

浏览器支持

迭代器辅助方法是 Baseline 2024:Chrome 122(2024 年 3 月)、Firefox 131(2024 年 9 月)、Safari 18.2(2024 年 12 月)、Node.js 22。对于这些版本之前的环境,core-js 3.38+ 包含了一个你可以添加到捆绑包中的 polyfill。

生成器函数已经返回一个迭代器——如果你今天正在编写生成器,它们的返回值会在你的目标足够现代时自动获得这些方法。

🎮 自己试试

▶️ 打开交互式游乐场 →

直接在浏览器中运行——操作它并实时观看概念反应。

🧠 自我测试

觉得理解了吗?参加 8 题测验 →

即时反馈,每个问题都有提示,每个答案的解释——无论正确与否。

要点

在你的代码库中搜索数组上的 .filter().map().slice(0, n) 链。每一个都会分配直接进入垃圾收集器的中间数组。用 Iterator.from().take(n) 替换它们,管道将在获得所需内容后立即停止——没有额外的工作,没有额外的内存。API 表面与数组方法几乎相同。唯一的转变是将链视为你正在描述的管道,而不是你正在转换的集合。


感谢阅读!让我们保持联系: