JavaScript flatMap 展开深度:每项能变成多条,为什么里面还有一层数组

前天 3阅读

一份样品清单里,每个样品拥有零个或多个颜色。导出时需要一条“样品加颜色”明细,普通 map 会得到数组套数组;flatMap 可以把每次回调返回的数组接到最终结果里。但它只展开一层,返回值多包一层方括号,结果中仍会留下数组。

下面先完成实际的一对多转换,再检查嵌套数组、字符串与类数组对象三种返回值。保存为 demo.js,运行 node demo.js。代码只处理内存里的固定记录,不修改文件或外部数据。

JavaScript flatMap 展开深度:每项能变成多条,为什么里面还有一层数组

AI生成概念插图:不同大小的输入托盘分别贡献零个、一个或多个明细块,内层小盒仍保留边界;不是界面截图。

const assert = require("node:assert/strict");
const samples = [
  { name: "A", colors: ["red", "blue"] },
  { name: "B", colors: [] },
  { name: "C", colors: ["green"] },
];
const details = samples.flatMap(sample =>
  sample.colors.map(color => ({ name: sample.name, color }))
);
assert.deepEqual(details, [
  { name: "A", color: "red" },
  { name: "A", color: "blue" },
  { name: "C", color: "green" },
]);
console.log("details:", JSON.stringify(details));

const nested = [1, 2].flatMap(value => [[value, value * 10]]);
assert.deepEqual(nested, [[1, 10], [2, 20]]);
console.log("one level:", JSON.stringify(nested));

const strings = [1, 2].flatMap(value => "x" + value);
assert.deepEqual(strings, ["x1", "x2"]);
console.log("strings:", JSON.stringify(strings));

const arrayLike = { 0: "red", 1: "blue", length: 2 };
const kept = [0].flatMap(() => arrayLike);
assert.equal(kept.length, 1);
assert.equal(kept[0], arrayLike);
console.log("array-like stays one item:", kept.length);

assert.deepEqual([1, 2, 3].flatMap(n => n % 2 === 0 ? [] : [n]), [1, 3]);
console.log("selected:", [1, 3]);

先约定一条输入能贡献几条输出

A 的回调返回两条明细,B 返回空数组,C 返回一条,flatMap 按输入顺序把它们接到同一个数组。因此结果共有三条,B 没有占据一个空对象位置。这里空数组表达“没有输出记录”;如果业务要求每个样品都保留一行,应明确给 B 产生一条带缺失标记的记录。

flatMap 同时承担映射与一层展开,所以可以用 [] 跳过输入、[value] 保留一条、多个元素的数组产生多条。末尾的奇数筛选只是验证零条和一条的规则。若只需要普通筛选,filter 往往更直观;是否选 flatMap 应看任务有没有一对多的转换需求。

展开只看当前层真正的数组

nested 中每次返回的是“数组里再放一个数组”,外面这一层被拆掉,里面的 [1, 10] 和 [2, 20] 仍然各作为一个元素。flatMap 没有可传的深度参数,不能在第二个参数里写数字来要求继续展开;那个参数用于指定回调的 this 值。

字符串返回值没有被拆成字符,类数组对象也仍是一个元素。即使对象有零、一和 length 属性,看起来很像数组,展开算法也不会因此把它当作 Array。需要转换时,应先根据输入协议显式生成数组,再交给这一步,避免把“能遍历”和“会展开”混为一谈。

示例中每条明细都新建了对象;flatMap 本身不负责深拷贝。若回调直接返回原对象引用,后续修改这些对象仍可能影响原资料。决定输出记录需要独立到哪一层,是转换函数自己的责任,不应把它藏在一个听起来像“整理数据”的函数名里。

回调也应保持同步转换的含义。若返回 Promise,flatMap 不会等待其完成,更不会自动展开它将来的数组结果。对于异步来源,应先安排好等待与错误处理,再对实际结果做展开。验收时同时检查记录数、每项类型与层级深度,能更早发现多包或少包一层的问题。

资料核对日期:2026年10月2日。代码在本地 Node.js v24.19.0 实际运行并通过断言;结果只对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。