别再把一切都转换成数组
大多数前端代码在数据呈现在屏幕之前就已经处理了它。我们获取列表、调整它、精简它,然后重复。而且通常我们没有仔细考虑在这个过程中做了多少工作。
多年来,现代 JavaScript 推动我们采用一个熟悉的模式:
data
.map(...)
.filter(...)
.slice(...)
.map(...)
这既可读又富有表现力。但它也是急切的,会分配多个数组,而且经常做不必要的工作。
JavaScript 中的**迭代器辅助函数(iterator helpers)**为我们提供了一种原生、惰性的替代方案,特别适合处理大型数据集、流和 UI 驱动的逻辑。
到处都是数组(以及做了不必要的工作)
考虑一下这个 UI 场景:
- 你获取一个大型数据集
- 你过滤它
- 你取前几个结果
- 你渲染它们
const visibleItems = items.filter(isVisible).map(transform).slice(0, 10);
看起来无害,对吧?我不止一次写过这样的链式调用。在底层:
filter创建一个新数组map创建另一个数组slice再创建一个数组
即使只需要 10 个项目,可能需要处理数千个数组项。这种不匹配就是整个问题所在。而这正是迭代器辅助函数发挥作用的地方。
那么,什么是迭代器辅助函数?
迭代器辅助函数是迭代器对象上的可链式调用的方法,而不是数组。
这个区别很重要。是的,一开始很容易忽略:数组不会自动获得这些方法。你需要从 values()、keys()、entries() 或生成器中获取一个迭代器。然后可以在它上面构建惰性工作流。
它们能够做以下事情:
mapfiltertakedropflatMapfind、some、everyreducetoArray
这些辅助函数大多是惰性的,意味着它们只在需要时才提取值。
⚠️ 注意:
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 实际行为的工作代码
一旦你习惯了惰性迭代,回到急切的链式调用会觉得有点浪费。
本文作者 Matt Smith,转载请注明来源链接:
原文链接: https://allthingssmitty.com/2026/01/12/stop-turning-everything-into-arrays-and-do-less-work-instead/
本文链接: https://tie.pub/blog/stop-turning-everything-into-arrays/