JavaScript Array.prototype.flatMap ():数组 “扁平化 + 映射” 的高效组合拳

在 JavaScript 数组处理中,我们经常需要先对每个元素进行转换(映射),再将结果 "铺平"(扁平化)。比如将数组中的每个字符串按空格拆分,然后合并成一个新数组。传统做法是先用map()转换,再用flat()扁平化,但这样会创建中间数组,影响效率。ES2019 引入的flatMap()方法,就像一套 "组合拳",将映射和扁平化两步操作合并为一,既简洁又高效。今天,我们就来解锁这个提升数组处理效率的实用方法。

一、认识 flatMap ():映射与扁平化的 "二合一" 工具

flatMap()是数组原型上的方法,它的作用可以概括为:先对数组中的每个元素执行映射操作(类似 map()),再对结果执行一层扁平化(类似 flat(1) 。简单说,flatMap()等价于map() followed by flat(1),但性能更优。

1.1 与 map () + flat () 的对比

js 复制代码
// 原始数组
const sentences = ["Hello world", "I love JavaScript", "flatMap is useful"];

// 方法1:map() + flat()
const words1 = sentences
  .map((sentence) => sentence.split(" ")) // 先映射:拆分成子数组
  .flat(); // 再扁平化:合并子数组

// 方法2:flatMap()
const words2 = sentences.flatMap((sentence) => sentence.split(" "));
console.log(words1); // ["Hello", "world", "I", "love", "JavaScript", "flatMap", "is", "useful"]
console.log(words2); // 同上,结果完全一致

两者结果相同,但flatMap()只遍历一次数组,且不创建中间数组(map()的结果),因此在处理大型数组时性能更优。

1.2 基础语法:简洁的回调函数

flatMap()的语法与map()类似,接收一个回调函数和可选的this指向:

js 复制代码
array.flatMap(callback(element[, index[, array]])[, thisArg])
  • callback:对每个元素执行的函数,返回一个数组(或其他可迭代对象),该数组会被扁平化一层。

  • thisArg:执行callback时的this值。

  • 返回值:经过映射和扁平化后的新数组。

示例:将数组元素翻倍并拆分

js 复制代码
const numbers = [1, 2, 3];

// 映射:每个元素翻倍后放在数组中;扁平化:合并子数组
const result = numbers.flatMap((num) => [num * 2, num * 3]);
console.log(result); // [2, 3, 4, 6, 6, 9]

回调函数返回[num * 2, num * 3]flatMap()会将这些子数组合并成一个数组。

二、核心特性:只扁平化一层的 "精准控制"

flatMap()的扁平化能力是有限的 ------ 它只会将映射结果扁平化一层 ,不会递归处理深层嵌套的数组。这一点与flat(depth)不同(flat()可指定深度),也是flatMap()的关键特性。

2.1 与 flat () 不同的扁平化深度

js 复制代码
const arr = [1, [2, [3]], 4];

// flatMap():只扁平化一层
const result1 = arr.flatMap((item) => {
  // 映射:原样返回元素(模拟不映射,只看扁平化效果)
  return item;
});

// flat(1):同样扁平化一层
const result2 = arr.flat(1);

// flat(2):扁平化两层
const result3 = arr.flat(2);

console.log(result1); // [1, 2, [3], 4](仅扁平一层)
console.log(result2); // [1, 2, [3], 4](与flatMap结果一致)
console.log(result3); // [1, 2, 3, 4](深层扁平)

可以看到,flatMap()的扁平化效果等价于flat(1),无法处理深层嵌套。如果需要深层扁平化,仍需在flatMap()之后调用flat(depth)

2.2 过滤元素的 "小技巧"

利用flatMap()只扁平化一层的特性,我们可以在映射时返回空数组[],实现 "过滤" 效果(空数组会被扁平化为空,相当于删除元素):

js 复制代码
const numbers = [1, 2, 3, 4, 5, 6];

// 保留偶数,过滤奇数(奇数返回空数组)
const evenNumbers = numbers.flatMap((num) => {
  return num % 2 === 0 ? [num] : [];
});

console.log(evenNumbers); // [2, 4, 6]

这比filter() + map()的组合更简洁(numbers.filter(num => num % 2 === 0).map(num => num)),且只遍历一次数组。

三、实战场景:flatMap () 的高效应用

flatMap()在需要同时进行映射和扁平化的场景中表现出色,以下是几个典型案例:

3.1 拆分字符串并去重

处理包含多个标签的字符串数组,拆分后去重:

js 复制代码
const tagGroups = ["html,css", "javascript,html", "css,react"];

// 拆分所有标签并去重
const uniqueTags = [...new Set(tagGroups.flatMap((group) => group.split(",")))];

console.log(uniqueTags); // ["html", "css", "javascript", "react"]
  • flatMap()先将每个字符串按,拆分,再合并成一个标签数组。

  • Set去重后转回数组,实现高效处理。

3.2 生成新的对象数组

将用户数组转换为 "用户名 - 邮箱" 对数组:

js 复制代码
const users = [
  { name: "Alice", emails: ["alice@a.com", "alice.work@a.com"] },
  { name: "Bob", emails: ["bob@b.com"] },
];

// 生成 [{ name: "Alice", email: "alice@a.com" }, ...]
const userEmails = users.flatMap((user) =>
  user.emails.map((email) => ({ name: user.name, email: email }))
);

console.log(userEmails);

/*
[
 { name: "Alice", email: "alice@a.com" },
 { name: "Alice", email: "alice.work@a.com" },
 { name: "Bob", email: "bob@b.com" }
]
*/

flatMap()先对每个用户映射出包含多个邮箱对象的数组,再合并成一个数组,避免了map()后额外的flat()操作。

3.3 处理树形结构数据

将嵌套的评论数据 "铺平" 为一维数组:

js 复制代码
const comments = [
  {
    id: 1,
    text: "第一条评论",
    replies: [
      { id: 11, text: "回复1" },
      { id: 12, text: "回复2" },
    ],
  },
  {
    id: 2,
    text: "第二条评论",
    replies: [],
  },
];

// 提取所有评论(包括回复)的id和text
const allComments = comments.flatMap((comment) => [
  // 先包含当前评论
  { id: comment.id, text: comment.text },
  // 再包含所有回复(会被扁平化)
  ...comment.replies.map((reply) => ({ id: reply.id, text: reply.text })),
]);

console.log(allComments);

/*
[
 { id: 1, text: "第一条评论" },
 { id: 11, text: "回复1" },
 { id: 12, text: "回复2" },
 { id: 2, text: "第二条评论" }
]
*/

通过flatMap()将嵌套的回复与主评论合并,一步完成映射和扁平化。

3.4 数据转换与过滤结合

处理订单数据,提取有效商品并转换格式:

js 复制代码
const orders = [
  { id: 1, products: ["apple", "banana"], valid: true },
  { id: 2, products: ["orange"], valid: false }, // 无效订单
  { id: 3, products: ["grape", "mango"], valid: true },
];

// 提取有效订单的商品,格式化为 { orderId, product }
const validProducts = orders.flatMap((order) => {
  // 过滤无效订单(返回空数组)
  if (!order.valid) return [];

  // 映射有效商品
  return order.products.map((product) => ({
    orderId: order.id,
    product: product,
  }));
});

console.log(validProducts);

/*
[
 { orderId: 1, product: "apple" },
 { orderId: 1, product: "banana" },
 { orderId: 3, product: "grape" },
 { orderId: 3, product: "mango" }
]
*/

一次遍历完成过滤和转换,效率高于filter() + map() + flat()的组合。

四、避坑指南:使用 flatMap () 的注意事项

4.1 不要期望深层扁平化

flatMap()只能扁平化一层,对于深层嵌套的数组,需要额外处理:

js 复制代码
const deepArray = [1, [2, [3, [4]]]];

// 错误:flatMap()无法深层扁平化
const result1 = deepArray.flatMap((item) => item);
console.log(result1); // [1, 2, [3, [4]]](仅扁平一层)

// 正确:先flatMap()再flat()
const result2 = deepArray.flatMap((item) => item).flat(2);
console.log(result2); // [1, 2, 3, 4]

4.2 回调函数需返回可迭代对象

flatMap()的回调函数应返回数组或其他可迭代对象(如字符串、Set等),否则会将返回值包装成数组再扁平化:

js 复制代码
const numbers = [1, 2, 3];

// 回调返回非数组(数字)
const result = numbers.flatMap((num) => num * 2);
console.log(result); // [2, 4, 6]

// 等价于:[ [2], [4], [6] ].flat() → [2,4,6]

虽然返回非数组也能工作,但建议始终返回数组,明确意图。

4.3 性能考量:大型数组的处理

flatMap()map() + flat()高效,但在处理超大型数组时,仍需注意:

  • 避免在回调函数中执行复杂操作(会增加单次迭代耗时)。

  • 如需多次处理,考虑分批次处理(避免阻塞主线程)。

4.4 浏览器兼容性

flatMap()兼容所有现代浏览器(Chrome 69+、Firefox 62+、Safari 12+、Edge 79+),但 IE 完全不支持。如需兼容旧浏览器,可使用 Polyfill:

js 复制代码
// flatMap()的简易Polyfill
if (!Array.prototype.flatMap) {
  Array.prototype.flatMap = function (callback, thisArg) {
    return this.map(callback, thisArg).flat(1);
  };
}

五、总结

flatMap()作为map()flat(1)的组合,虽然功能看似简单,却在数组处理中有着不可替代的价值:

  • 简洁高效:一行代码完成映射和扁平化,减少中间数组创建。

  • 灵活多用:既能转换数据,又能过滤元素,还能处理嵌套结构。

  • 性能更优 :比map() + flat()少一次遍历,适合大型数组。

在实际开发中,当你需要对数组元素进行转换并希望结果是一维数组时,flatMap()往往是最佳选择。无论是处理字符串拆分、对象转换,还是嵌套数据铺平,它都能让代码更简洁、高效。

记住:好的工具能让复杂问题变简单,flatMap()就是这样一个 "四两拨千斤" 的数组方法。下次处理数组时,不妨想想:这个场景能用flatMap()吗?

你在项目中用过flatMap()解决什么问题?欢迎在评论区分享你的经验~

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端