原文链接:allthingssmitty.com/2026/01/12/…
作者:Matt Smith
大多数前端代码在数据呈现于屏幕之前就已完成处理。我们获取列表、调整内容、精简结构,周而复始。而通常我们并未深思过程中所耗费的精力。
多年来,现代JavaScript不断引导我们遵循一个熟悉的模式:
data
.map(...)
.filter(...)
.slice(...)
.map(...)
它可读性强且富有表现力。但它也过于积极,会分配多个数组,并常执行不必要的操作。
JavaScript中的迭代器辅助函数为我们提供了原生的惰性替代方案,尤其适用于处理大型数据集、数据流和用户界面驱动的逻辑。
数组无处不在(且工作量远超必要)
考虑这个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在循环中的应用,以及如何高效构建异步逻辑。
更简洁的数据管道(无需辅助库)
在迭代器辅助工具出现前,你可能需要借助库才能实现懒加载管道。如今这已成为语言特性:
const ids = users
.values()
.map(u => u.id)
.filter(Boolean)
.toArray();
可读性强、原生支持、零依赖。
迭代器辅助函数与数组方法
| 数组方法 | 迭代器辅助工具 |
|---|---|
| 立即加载 | 延迟加载 |
| 分配新数组 | 最小化分配 |
| 始终处理所有项 | 可提前停止 |
| 熟悉 | 略有学习曲线 |
经验法则:若无需使用整个数组,则无需创建数组。
何时不应使用迭代器辅助工具
迭代器辅助工具虽强大,却不能在所有场景替代数组。若强行在每种情况使用,只会降低代码可读性。以下情况尤其不适用:
- 需要随机访问(
items[5]) - 高度依赖数组变异操作
- 数据规模较小且追求简洁性
你应该了解的注意事项
迭代器辅助函数在几个重要方面与数组的行为不同。
| 注意事项 | 含义 | 重要性 |
|---|---|---|
| 一次性迭代器 | 消耗后即失效 | 无法重复使用同一管道 |
| 延迟执行 | 消耗前不运行 | 副作用可能看似"缺失" |
| 仅支持顺序访问 | 无随机访问 | items[5]等模式无法转换 |
| 调试会消耗数据 | 日志记录可能推进迭代器 | console.log会改变行为 |
请将迭代器视为尚未发生的工作,而非已有的数据。
今天就能用吗?
迭代器辅助函数在所有现代浏览器和 Node 22+ 中均受支持。只要你针对的是当前版本,就完全没问题。
刻意减少工作量
长期以来,JavaScript 让我们养成了将一切急切转换为数组的习惯。迭代器辅助工具提供了另一种选择:
- 减少工作量
- 降低内存占用
- 编写符合实际 UI 行为的管道
一旦习惯了懒惰迭代,再回到急切链式操作时,会觉得有些浪费。