JavaScript 数组扁平化:flat、reduce 与递归的 4 种写法及避坑指南

编程狮(w3cschool.cn) 2026-09-08 14:55:19 浏览数 (18)
反馈

JavaScript 数组扁平化最直接的写法是 Array.prototype.flat:指定层级用 flat(depth),未知层级可用 flat(Infinity)。需要兼容旧环境、同时转换元素或理解实现原理时,再考虑 concat、reduce 或递归。

JavaScript 数组扁平化四种写法封面图

接口返回了多层分类数据,你只想得到一维列表;或者表格批量选择结果是数组套数组,提交前必须展开。数组扁平化看似简单,但空位、层级、超深嵌套和非数组值都会改变结果。本文用四种可运行写法比较 JavaScript 数组扁平化的边界和性能取舍。今天这篇文章,编程狮带你从“会写”走到“知道为什么这样选”。

一、先确定数组要展开多少层

数组扁平化是把嵌套数组中的元素提升到更外层。开始写代码前要先确定目标:只展开一层、固定展开两三层,还是不确定层级并希望得到完全一维数组。

const source = [1, [2, [3, [4]]]];

console.log(source.flat());         // [1, 2, [3, [4]]]
console.log(source.flat(2));        // [1, 2, 3, [4]]
console.log(source.flat(Infinity)); // [1, 2, 3, 4]

flat 的默认 depth 是 1。很多错误不是方法不会用,而是没有把输出层级写进需求。如果你还不熟悉数组方法,可以先看 JavaScript 基础教程,再回到本文比较方案。

⚠️ 注意:flat 会跳过数组中的空位。空位与值为 undefined 的元素不是同一回事,依赖索引位置时必须先测试数据。

二、写法一:用 flat 处理固定或未知层级

现代环境中,flat 是首选。它表达清楚,不需要维护递归状态。固定层级时传入数字,确认要完全展开时传入 Infinity。

const categoryIds = [[101, 102], [201, [202, 203]]];

const twoLevels = categoryIds.flat(2);
console.log(twoLevels); // [101, 102, 201, 202, 203]

不要习惯性使用 Infinity。如果数据层级本应最多为 2,固定 depth 能保留异常结构,让你更早发现接口返回错误。Infinity 更适合树深度确实不固定,并且叶子值才是最终目标的场景。

flat 只展开真正的数组,不会把普通可迭代对象或字符串拆开。这个边界通常是好事,因为它不会意外改变业务对象。

三、写法二:flatMap 同时转换并展开一层

flatMap 等价于先 map,再 flat(1),适合“每个输入生成零个或多个输出”的场景。例如把课程分组转换成带分组名的课程列表,或者在转换时过滤无效项。

const groups = [
  { name: "前端", lessons: ["HTML", "CSS"] },
  { name: "编程", lessons: ["JavaScript"] }
];

const lessons = groups.flatMap(group =>
  group.lessons.map(title => ({ group: group.name, title }))
);

console.log(lessons);

这段代码只展开 map 返回结果的一层。如果 lessons 自己还有多层嵌套,flatMap 不会递归处理。它的优势是意图明确:转换和一层展开属于同一个业务动作。

需要核对 flat、flatMap 或 reduce 的参数时,JavaScript 速查手册 可以直接查方法签名。

四、写法三:reduce 配合 concat 兼容旧环境

旧环境没有 flat 时,可以用 reduce 累加结果,再用 concat 展开一层。这个写法适合层级固定、兼容目标明确的项目。

function flattenOneLevel(list) {
  return list.reduce((result, item) => result.concat(item), []);
}

const source = [1, [2, 3], 4, [5]];
console.log(flattenOneLevel(source)); // [1, 2, 3, 4, 5]

concat 遇到数组会展开一层,遇到普通值则直接追加。它不会自动处理更深层级。需要多层时可以重复调用,但重复次数应和业务层级一致,不要通过 while 循环盲目展开未知数据。

这种方案的可读性不如 flat,但在需要兼容旧浏览器且不方便加入 polyfill 时很实用。项目已有构建工具时,通常更适合使用标准写法并交给兼容配置处理。

五、写法四:递归或迭代处理任意深度

手写递归能加入过滤、路径记录和最大深度限制。下面的实现只展开数组,其他值原样保留:

function flattenDeep(input) {
  const result = [];

  function visit(items) {
    for (const item of items) {
      if (Array.isArray(item)) {
        visit(item);
      } else {
        result.push(item);
      }
    }
  }

  visit(input);
  return result;
}

console.log(flattenDeep([1, [2, [3, [4]]]]));

递归写法容易理解,但极端深度可能触发调用栈限制。数据深度来自用户输入或不可信来源时,可以用显式栈改写:

function flattenWithStack(input) {
  const stack = [...input].reverse();
  const result = [];

  while (stack.length > 0) {
    const item = stack.pop();
    if (Array.isArray(item)) {
      for (let i = item.length - 1; i >= 0; i -= 1) {
        stack.push(item[i]);
      }
    } else {
      result.push(item);
    }
  }
  return result;
}

显式栈避免函数层层调用,并通过逆序压栈保持原始元素顺序。它的代码更长,只有在深度不可控或需要遍历过程信息时才值得使用。

5.1 4 种数组扁平化写法怎么选

写法 展开层级 适合场景 主要注意点
flat 固定或全部 现代环境的一般场景 会跳过空位
flatMap 一层 转换后产生多个结果 只能展开一层
reduce + concat 一层 旧环境兼容 可读性略低
递归或栈 任意 自定义规则、深度不可控 需要自己测试边界

不要只用小数组做性能结论。真实选择通常先看可读性和兼容性,再看数据规模。对于几十或几百个元素,标准方法带来的清晰度往往比微小耗时差异更重要;对于大数据,应使用实际样本测量内存和耗时。

还要注意去重不是数组扁平化的一部分。展开后的重复元素会被保留。如果业务需要唯一值,应在扁平化完成后再根据数据类型选择 Set、Map 或键函数处理。关于这一点,可以参考 JavaScript 数组去重笔记

JavaScript 数组扁平化方法选择决策树

总结

JavaScript 数组扁平化先看目标层级,再选实现。现代环境优先 flat;转换并展开一层用 flatMap;旧环境可以用 reduce 配合 concat;需要自定义规则或应对未知深度时,再选择递归或显式栈。

你需要带走三点:depth 应反映业务结构;空位和 undefined 要分开测试;扁平化、过滤和去重是不同动作。下一步可以把项目中的嵌套数组样本加入测试,确保方法升级后输出顺序和类型保持一致。

延伸学习

想系统掌握数组与工程写法,可以按这个顺序继续:

  1. 先学 JavaScript 原生模块课程,把工具函数组织成可复用模块;
  2. 再读 JavaScript 判断空数组,补齐数组边界判断;
  3. 最后用 JavaScript 实战项目 把扁平化、筛选和渲染串成完整练习。

常见问题

Q:flat(Infinity) 会很慢吗?

不一定。耗时取决于数组规模、深度和运行时。普通业务数据通常无需担心;数据非常大或深度不可控时,应使用真实样本测量,并考虑显式栈避免调用栈问题。

Q:flat 会修改原数组吗?

不会。flat 返回一个新数组,原数组结构保持不变。但数组里的对象元素仍是原对象引用,修改这些对象属性仍可能影响原数据。

Q:flatMap 能完全替代 map 加 flat 吗?

只能替代 map 后再展开一层的情况。需要展开两层以上时,仍要继续调用 flat 或换用其他方案。flatMap 更适合一个输入产生零个或多个输出的表达。

Q:为什么数组空位展开后消失了?

这是 flat 的标准行为。它会移除被处理层级中的空位,但不会移除显式写入的 undefined。依赖索引位置时,应先把空位转换成明确值或避免使用 flat。

0 人点赞