先记住这个答案
flatMap 会把每一项的回调结果展开一层再拼起来,所以回调返回空数组就等于删除这一项,返回单元素数组等于替换,返回多个元素等于一项变多项。map 只能一对一,filter 只能决定留还是不留,两者都没法把一项变成多项。所以两类场景更适合 flatMap:一是过滤条件要依赖转换后的结果,比如解析失败的丢掉;二是一条记录要展开成多条,比如订单拆成商品行。至于性能,它少了一个中间数组,但每项都会新建小数组,是不是更快要实测。
- 回调返回
[]删除,[x]保留,[a, b]展开 - 过滤要依赖转换结果时,用
flatMap只算一次 - 一条记录生成多行展示数据,天然适合
flatMap - 返回
null不会被过滤掉,必须返回[]
过滤依赖转换结果时
从一组字符串里解析出合法的数字,解析失败的丢掉。用 map 加 filter 要先把所有项都转一遍,再筛一遍。
const inputs = ['1', 'x', '3']
// map + filter:两次遍历,中间多一个数组
const a = inputs.map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n))
// flatMap:一次遍历,不要的返回 []
const b = inputs.flatMap((s) => {
const n = parseInt(s, 10)
return Number.isNaN(n) ? [] : [n]
})
console.log(JSON.stringify(a), JSON.stringify(b))查看输出与解释
[1,3] [1,3]两种写法结果一样。map 加 filter 的问题在于,筛选用的是转换后的值,所以必须先转完。如果反过来先 filter 再 map,就得在两个回调里各解析一次。flatMap 把「算出来、判断要不要、放进结果」写在同一个回调里,逻辑集中。
在 TypeScript 里这个写法还有个好处:filter 想把 (number | null)[] 收窄成 number[],通常要写类型谓词;flatMap 返回 [] 或 [n],推断出来直接就是 number[]。
一条记录展开成多条
这是 flatMap 真正不可替代的地方。比如订单列表要渲染成「每个商品一行」。
const orders = [
{ id: 'A', items: ['笔', '本'] },
{ id: 'B', items: [] }, // 没有商品,直接消失
{ id: 'C', items: ['尺'] }
]
const rows = orders.flatMap((o) => o.items.map((name) => `${o.id}-${name}`))
console.log(JSON.stringify(rows))查看输出与解释
["A-笔","A-本","C-尺"]A 变成两行,B 因为 items 是空数组直接消失,C 是一行。用 map 会得到 [["A-笔","A-本"],[],["C-尺"]],还得再 flat 一次。类似的场景还有分词、把时间区间拆成每一天、树形菜单展开成扁平列表的一层。
- 什么时候反而不该用
- 如果只是「先过滤再转换」,而且过滤条件和转换无关,比如
users.filter((u) => u.active).map((u) => u.name),这时filter加map一眼就能看懂。改成flatMap((u) => (u.active ? [u.name] : []))反而要多想一下。可读性优先,别为了少一次遍历牺牲清晰。
容易答错的地方
- 用返回
null或undefined来过滤 flatMap只展开数组,返回非数组值会原样作为一项保留。[1, 2, 3].flatMap((x) => (x > 1 ? x : null))得到[null, 2, 3],其中null还在。想删掉必须返回[]。- 说
flatMap一定比map加filter快 - 它少了一个完整的中间数组,但每次回调都新建了
[]或[n]这样的小数组,分配次数不一定更少。实际快慢取决于数据规模和引擎优化。真到了性能热点,最省的是普通for循环直接push进结果数组。
面试官还会怎么问?
flatMap 能展开两层吗?
不能,深度固定为 1。回调返回 [[1, 2]],结果里是 [1, 2] 这一项,不会继续拆开。要多层展开,只能写 map(fn).flat(depth)。
用 reduce 也能同时过滤和映射,和 flatMap 比怎么样?
reduce 更通用,累加器可以是任何结构,但写法更啰嗦,要自己 push 或拼接再 return 累加器。结果就是一个数组时,flatMap 表达得更直接;需要同时产出计数、分组这类非数组结构时,才用 reduce。
回调里可以用 await 吗?
可以写,但 flatMap 不会等待。回调返回的是 Promise,不是数组,结果是一个 Promise 数组。异步场景要先用 Promise.all(arr.map(asyncFn)) 拿到结果,再对结果调用 flat 或 flatMap。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。