吴文俊
吴文俊
关注前端编码,想要学习开发 Java, Android-iOS App.
下雪的冬季里的雾凇

别再把一切都转换成数组

大多数前端代码在数据呈现在屏幕之前就已经处理了它。我们获取列表、调整它、精简它,然后重复。而且通常我们没有仔细考虑在这个过程中做了多少工作。

多年来,现代 JavaScript 推动我们采用一个熟悉的模式:

data
  .map(...)
  .filter(...)
  .slice(...)
  .map(...)

这既可读又富有表现力。但它也是急切的,会分配多个数组,而且经常做不必要的工作

JavaScript 中的**迭代器辅助函数(iterator helpers)**为我们提供了一种原生、惰性的替代方案,特别适合处理大型数据集、流和 UI 驱动的逻辑。

到处都是数组(以及做了不必要的工作)

考虑一下这个 UI 场景:

  • 你获取一个大型数据集
  • 你过滤它
  • 你取前几个结果
  • 你渲染它们
const visibleItems = items.filter(isVisible).map(transform).slice(0, 10);

看起来无害,对吧?我不止一次写过这样的链式调用。在底层:

  1. filter 创建一个新数组
  2. map 创建另一个数组
  3. slice 再创建一个数组

即使只需要 10 个项目,可能需要处理数千个数组项。这种不匹配就是整个问题所在。而这正是迭代器辅助函数发挥作用的地方。

那么,什么是迭代器辅助函数?

迭代器辅助函数是迭代器对象上的可链式调用的方法,而不是数组。

这个区别很重要。是的,一开始很容易忽略:数组不会自动获得这些方法。你需要从 values()keys()entries() 或生成器中获取一个迭代器。然后可以在它上面构建惰性工作流。

它们能够做以下事情:

  • map
  • filter
  • take
  • drop
  • flatMap
  • findsomeevery
  • reduce
  • toArray

这些辅助函数大多是惰性的,意味着它们只在需要时才提取值。

⚠️ 注意: reduce 会急切地消耗迭代器,因为它必须看到每个值才能产生结果。

一般来说,惰性意味着:

  • 没有中间数组
  • 没有不必要的工作
  • 最重要的是,事情会在尽可能早的时候停止

描述想要什么,运行时只在需要时提取值。

默认惰性

下面是使用迭代器辅助函数的相同逻辑:

const visibleItems = items
  .values()
  .filter(isVisible)
  .map(transform)
  .take(10)
  .toArray();

那么这里实际改变了什么?

  • items.values() 返回一个迭代器,而不是数组
  • 每一步仅在请求下一个值时运行
  • 处理在匹配 10 个后停止

在实际应用中这给你带来了什么

这并不总是关乎原始速度,而是关乎避免不必要的工作。迭代器辅助函数解锁了更好的 UI 模式

渲染大型列表

如果正在处理:

  • 虚拟化列表
  • 无限滚动
  • 大型表格

惰性迭代意味着不会处理永远不会到达屏幕的项目:

function* rows(data) {
  for (const row of data) {
    yield renderRow(row);
  }
}

const visibleRows = rows(data).filter(isInViewport).take(20).toArray();

只渲染需要的东西,不多也不少。

流和异步数据

异步可迭代对象有自己的迭代器辅助函数,这使它们非常适合分页 API 和流:

async function* fetchPages() {
  let page = 1;
  while (true) {
    const res = await fetch(`/api/items?page=${page++}`);
    if (!res.ok) return;
    yield* await res.json();
  }
}

const firstTen = await fetchPages().filter(isValid).take(10).toArray();

不需要缓冲整个响应,不需要手动计数器。只需描述流程,让运行时提取需要的内容。

💡 准备好了解更多了吗?

了解 await 在循环中如何工作,以及如何高效构建异步逻辑

更干净的数据流动(无需工具库)

在迭代器辅助函数之前,可能会使用库来获得惰性工作流。现在它已经是 js 语言的一部分:

const ids = users
  .values()
  .map((u) => u.id)
  .filter(Boolean)
  .toArray();

可读、原生、零依赖。

迭代器辅助函数 vs 数组方法

数组方法迭代器辅助函数
急切惰性
分配新数组最小化分配
始终处理所有项目可以提前停止
熟悉学习曲线略有不同

经验法则: 如果不需要整个数组,就不要创建它。

何时使用迭代器辅助函数

迭代器辅助函数很强大,但它们并不是在所有地方都替代数组。如果试图在每种情况下都强行使用它们,只会让代码更难读。它们在以下情况下不适合:

  • 你需要随机访问(items[5]
  • 你大量依赖数组变异
  • 你的数据规模很小,简单性更重要

你应该知道的陷阱

迭代器辅助函数在几个重要方面与数组的行为不同。

陷阱意味着什么为什么重要
一次性迭代器一旦被消耗,就结束了你不能重复使用同一个工作流两次
惰性执行在消耗之前什么都不运行副作用可能看起来”丢失”了
仅顺序访问没有随机访问items[5] 这样的模式无法转换
调试会消耗数据日志记录可能会推进迭代器console.log 可能会改变行为

把迭代器想象成尚未发生的工作,而不是已经拥有的数据。

今天就可以使用吗?

迭代器辅助函数在所有现代浏览器和 Node 22+ 中都受支持。如果超过当前版本,就没问题。

有意识地少做工作

很长一段时间,JavaScript 急切地把一切都转换成数组。迭代器辅助函数给我们提供了另一个选择:

  • 做更少的工作
  • 分配更少的内存
  • 编写匹配 UI 实际行为的工作代码

一旦你习惯了惰性迭代,回到急切的链式调用会觉得有点浪费。