JavaScript flatMap 展开深度:每项能变成多条,为什么里面还有一层数组
一份样品清单里,每个样品拥有零个或多个颜色。导出时需要一条“样品加颜色”明细,普通 map 会得到数组套数组;flatMap 可以把每次回调返回的数组接到最终结果里。但它只展开一层,返回值多包一层方括号,结果中仍会留下数组。
下面先完成实际的一对多转换,再检查嵌套数组、字符串与类数组对象三种返回值。保存为 demo.js,运行 node demo.js。代码只处理内存里的固定记录,不修改文件或外部数据。
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 实际运行并通过断言;结果只对应文中给定输入。


