【翻译】别再把所有东西都变成数组了(不妨少做些无用功)

3 阅读5分钟

原文链接: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);

看起来无害,对吧?我写过太多这样的链式操作,多到都不好意思承认。但背后真相是:

  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 在循环中的应用,以及如何高效构建异步逻辑

更简洁的数据管道(无需辅助库)

在迭代器辅助工具出现前,你可能需要借助库才能实现懒加载管道。如今这已成为语言特性:

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

可读性强、原生支持、零依赖。

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

数组方法迭代器辅助工具
立即加载延迟加载
分配新数组最小化分配
始终处理所有项可提前停止
熟悉略有学习曲线

经验法则:若无需使用整个数组,则无需创建数组。

何时应使用迭代器辅助工具

迭代器辅助工具虽强大,却不能在所有场景替代数组。若强行在每种情况使用,只会降低代码可读性。以下情况尤其不适用:

  • 需要随机访问(items[5]
  • 高度依赖数组变异操作
  • 数据规模较小且追求简洁性

你应该了解的注意事项

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

注意事项含义重要性
一次性迭代器消耗后即失效无法重复使用同一管道
延迟执行消耗前不运行副作用可能看似"缺失"
仅支持顺序访问无随机访问items[5]等模式无法转换
调试会消耗数据日志记录可能推进迭代器console.log会改变行为

请将迭代器视为尚未发生的工作,而非已有的数据。

今天就能用吗?

迭代器辅助函数在所有现代浏览器和 Node 22+ 中均受支持。只要你针对的是当前版本,就完全没问题。

刻意减少工作量

长期以来,JavaScript 让我们养成了将一切急切转换为数组的习惯。迭代器辅助工具提供了另一种选择:

  • 减少工作量
  • 降低内存占用
  • 编写符合实际 UI 行为的管道

一旦习惯了懒惰迭代,再回到急切链式操作时,会觉得有些浪费。