先记住这个答案
Array.prototype.flat 的默认深度是 1,即只把第一层子数组的元素拼接进新数组,更深层的嵌套保持原样;需要更深可传入 2、3 或 Infinity。flatMap(callback) 在语义上相当于 arr.map(callback).flat(1):先对每个元素执行回调,再把返回的数组展平一层。区别在于 flatMap 只遍历一次、不产生 map 的中间数组,且深度固定为 1 不可调整。由于回调可以返回任意长度的数组,包括空数组,flatMap 能同时完成映射与过滤,使结果长度与输入不同,这是普通 map 做不到的。
- flat 不传参默认只展平一层嵌套
- flatMap 等价于 map 后接深度 1 的 flat
- flatMap 返回空数组即可过滤,改变结果长度
flat 的深度参数与 flatMap 的两步语义
flat(depth) 会创建一个新数组,把子数组的元素递归拼接到指定深度为止。depth 缺省值是 1,所以 [1, [2, [3]]].flat() 得到 [1, 2, [3]],第三层数组原样保留。它是复制方法,不修改原数组,只返回包含相同元素的浅拷贝。
flatMap(cb) 内部先对每个元素调用回调,把返回值(通常是数组)收集起来,再执行恰好一层的展平。它只遍历一次原数组,不会像 map(...).flat() 那样先分配一个完整的中间二维数组,因此在长链式处理中少一次分配和一次拷贝,但展平能力被锁死在一层。
const nested = [1, [2, [3, [4]]]];
console.log(JSON.stringify(nested.flat()));
console.log(JSON.stringify(nested.flat(2)));
console.log(JSON.stringify(nested.flat(Infinity)));
const words = ['a b', '', 'c d e'];
const parts = words.flatMap(s => (s === '' ? [] : s.split(' ')));
console.log(JSON.stringify(parts));
console.log(parts.length, words.length);查看输出与解释
[1,2,[3,[4]]]
[1,2,3,[4]]
[1,2,3,4]
["a","b","c","d","e"]
5 3这段代码在任意现代浏览器控制台或 Node.js 中可直接运行。前三次打印展示深度 1、2、Infinity 的差异;后半段用 flatMap 把字符串拆词并丢弃空串,结果长度 5 大于输入长度 3,直观说明长度可变。
接口返回的分组数据需要条件拆平并剔除无效项
假设前端拿到接口返回的订单分组:[[{id:1}], [], [{id:2}, {id:3}]],要求交给表格组件的是一层订单数组且丢弃空分组。由于嵌套只有一层,groups.flat() 即可;但若同时要做映射,例如把每组转换为 {id, label} 列表并跳过空组,flatMap 一趟就能完成,避免先 map 再 flat 的两趟遍历。
选择 flatMap 的决策依据是:映射和展平在同一趟循环里完成,中间不产生存放分组数组的临时数组,数据量大时减少内存分配;同时回调对空分组返回 [],展平后该组自然消失,无需再写 filter。最终得到 [{id:1,label:...}, {id:2,...}, {id:3,...}],长度由回调返回值决定。
const groups = [
[{ id: 1, name: 'A' }],
[],
[{ id: 2, name: 'B' }, { id: 3, name: 'C' }]
];
const rows = groups.flatMap(g =>
g.map(o => ({ id: o.id, label: '#' + o.id + ' ' + o.name }))
);
console.log(JSON.stringify(rows));
console.log(rows.length);查看输出与解释
[{"id":1,"label":"#1 A"},{"id":2,"label":"#2 B"},{"id":3,"label":"#3 C"}]
3代码可直接在浏览器或 Node.js 运行。flatMap 的回调把每组映射为行对象数组,空组映射为空数组并在展平时被消除,最终输出 3 行,无需额外的 filter 步骤。
深度超过一层、稀疏数组与泛用性的失效点
flatMap 最常见的失效条件是嵌套超过一层:它固定只展平一层,回调返回 [[1, 2]] 时结果仍是 [[1, 2]],此时必须改写成 map(cb).flat(2) 或 flat(Infinity)。另外若回调返回的不是数组而是单个值,该值会被直接保留,不再展平,容易被误认为发生了递归。
稀疏数组也是边界:flat 会移除展平层级内的空位,[1, , [2, , 3]].flat() 得到 [1, 2, 3],空位被压缩掉,与显式存 undefined 的行为不同。flat 还是泛用方法,可用于有 length 和整数键的类数组对象,但只有真正的数组元素会被展平,类数组子元素原样保留,处理 DOM 集合转换来的数据时要留意。
容易答错的地方
- 以为 flat() 不传参会展平所有层级
- 错误。
depth缺省为 1,[1, [2, [3]]].flat()结果为[1, 2, [3]],更深层保持嵌套。要完全展平需显式传入足够大的数字或Infinity,否则深层数据仍是数组。 - 认为 flatMap 与 map 返回类型必须一致
flatMap回调可以返回任意长度的数组或单个值:返回[]等价于过滤掉该元素,返回多元素数组则扩充结果,因此输出长度可与输入不同;而map的输出长度恒等于输入长度。
面试官还会怎么问?
flat 会修改原数组吗?
不会。flat 是复制方法,返回一个新数组,原数组及其嵌套结构保持不变;但要注意元素是浅拷贝,嵌套对象仍与原数组共享引用。
flatMap 能指定展平深度吗?
不能。flatMap 内部固定只做一层展平,没有深度参数。若映射结果存在两层以上嵌套,只能拆成 map(cb).flat(n) 手动控制深度。
flat 对稀疏数组的空位如何处理?
在展平涉及的层级内空位会被移除,例如 [1, , [2, , 3]].flat() 得到 [1, 2, 3];更深未展平层级中的空位随子数组一起保留,这与显式 undefined 元素被保留的行为不同。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。