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

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

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

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

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

// 原始数组
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指向:

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

  • thisArg:执行callback时的this值。

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

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

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 () 不同的扁平化深度

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()只扁平化一层的特性,我们可以在映射时返回空数组[],实现 “过滤” 效果(空数组会被扁平化为空,相当于删除元素):

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 拆分字符串并去重

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

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 生成新的对象数组

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

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 处理树形结构数据

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

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 数据转换与过滤结合

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

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()只能扁平化一层,对于深层嵌套的数组,需要额外处理:

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等),否则会将返回值包装成数组再扁平化:

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:

// 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()解决什么问题?欢迎在评论区分享你的经验~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95550.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95550.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

区块链与元宇宙:数字资产的守护者

1 区块链支撑元宇宙数字资产的底层逻辑1.1 不可篡改性构建信任基石区块链的不可篡改性为元宇宙数字资产提供了坚实的信任基础。其核心在于分布式账本技术,当一笔数字资产交易发生时,会被打包成区块并广播至网络中的所有节点。每个节点都会对这笔交易进行…

Linux软件编程:进程和线程(进程)

进程一、基本概念进程:是程序动态执行过程,包括创建、调度、消亡程序:存放在外存的一段数据的集合二、进程创建(一)进程空间分布每个进程运行起来后,操作系统开辟0-4G的虚拟空间进程空间:用户空…

Mybatis学习笔记(五)

分页插件与性能优化 分页插件配置 简要描述:MybatisPlus分页插件是基于物理分页实现的高性能分页解决方案,支持多种数据库的分页语法,能够自动识别数据库类型并生成对应的分页SQL。 核心概念: 物理分页:直接在SQL层面进…

企业可商用的conda:「Miniforge」+「conda-forge」

文章目录一、彻底卸载现有 Anaconda/Miniconda二、安装 Miniforge(推荐)macOS/Linux检查Windows检查三、将通道固定为 conda-forge(严格优先)四、验证是否仍引用 Anaconda 源五、常见问题(FAQ)六、参考命令…

Flutter ExpansionPanel组件(可收缩的列表)

可以展开或者收缩的面板组件,收缩面板组件效果由ExpansionPanelList组件和ExpansionPanel组件共同完成。 ExpansionPanelList属性说明属性说明children子元素expansionCallback设置回调事件ExpansionPanel属性说明headerBuilder收缩的标题body内容isExpanded设置内容…

C/C++ 进阶:深入解析 GCC:从源码到可执行程序的魔法四步曲

引言距离上一篇博客更新已经过去了大概一两周的时间,而对于 Linux 系统的基本指令以及 Shell 编程的学习其实基本讲解完毕,Linux基础一块的知识就将告一段落了,如果有细节性的知识,我也会及时分享给各位,作为一名正在攀…

云服务器运行持续强化学习COOM框架的问题

1 环境要求 下载地址:https://github.com/TTomilin/COOM tensorflow 2.11以上 python 3.9以上 tensorflow2.12.0,需要安装tensorflow-probability0.19 2 修改代码 COOM/wrappers/reward.py 将 from gym import RewardWrapper修改为 from gymnasium impor…

MyBatis Interceptor 深度解析与应用实践

MyBatis Interceptor 深度解析与应用实践 一、MyBatis Interceptor概述 1.1 什么是MyBatis Interceptor MyBatis Interceptor,也称为MyBatis 插件,是 MyBatis 提供的一种扩展机制,用于在 MyBatis 执行 SQL 的过程中插入自定义逻辑。它类似…

【自动化测试】Web自动化测试 Selenium

🔥个人主页: 中草药 🔥专栏:【Java】登神长阶 史诗般的Java成神之路 测试分类 了解各种各样的测试方法分类,不是为了墨守成规按照既定方法区测试,而是已了解思维为核心,并了解一些专业名词 根…

2025 电赛 C 题完整通关攻略:从单目标定到 2 cm 测距精度的全流程实战

摘要 2025 年全国大学生电子设计竞赛 C 题要求“仅用一颗固定摄像头”在 5 s 内完成 100 cm~200 cm 距离、误差 ≤2 cm 的单目测距,并实时显示功耗。本文整合国一选手方案、CSDN 高分博文、B 站实测视频及官方说明,给出从硬件选型→离线标定→在线算法→…

Day 10: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现

Day 10-2: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现 📚 今日学习目标 掌握GPT架构组装:将Transformer组件组装成完整的生成模型 理解生成式预训练:掌握自回归语言建模的核心机制 端到端代码实现:从数据预处理到模型训练的完整流程 文本生成实战:训练Mi…

深入解析Prompt缓存机制:原理、优化与实践经验

深入解析Prompt缓存机制:原理、优化与实践经验 概述 在大型语言模型应用中,API请求的延迟和成本始终是开发者关注的核心问题。Prompt缓存(Prompt Caching)技术通过智能地复用重复内容,有效减少了API响应时间和运行成本…

CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解

CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解第1行:from posixpath import join第2行:from torch.utils.data import DataLoader第3行:import os第4行:import sys第5行:import random第6行&a…

Mybatis学习笔记(七)

Spring Boot集成 简要描述:MyBatis-Plus与Spring Boot的深度集成,提供了自动配置、启动器等特性,大大简化了配置和使用。 核心概念: 自动配置:基于条件的自动配置机制启动器:简化依赖管理的starter配置属性…

机器人伴侣的智能升级:Deepoc具身智能模型如何重塑成人伴侣体验

引言:机器人伴侣市场的技术变革需求随着人工智能技术的飞速发展和人们情感需求的多元化,机器人成人伴侣市场正在经历前所未有的增长。传统机器人伴侣已经能够满足基础的交互需求,但在智能化、情感化和个性化方面仍存在明显不足。这正是深算纪…

metabase基础使用技巧 (dashboard, filter)

这是metabase系列分享文章的第2部分。本文将介绍metabase的基础概念和使用介绍 question question是metabase中提供的通过UI化操作就能实现简单的 快捷 直接的BI查询。 点击右侧的New -> Question即可创建Question,可以理解为一个格式化的查询: 这里…

机器人成人伴侣的智能化升级:Deepoc具身模型赋能沉浸式体验

引言:成人机器人市场的技术革新需求随着人工智能和机器人技术的快速发展,成人陪伴机器人行业正经历从简单机械运动到智能化交互的转型。据市场研究数据显示,全球成人机器人市场规模预计将在2026年突破100亿美元,年复合增长率保持在…

Go语言企业级权限管理系统设计与实现

最近跟着学长再写河南师范大学附属中学图书馆的项目,学长交给了我一个任务,把本项目的权限管理给吃透,然后应用到下一个项目上。 我当然是偷着乐呐,因为读代码的时候,总是莫名给我一种公费旅游的感觉。 本来就想去了解…

Java应用快速部署Tomcat指南

将Java应用部署到Apache Tomcat服务器是开发Web应用过程中常见的任务。Tomcat是一个免费且开源的Servlet容器,它为Java应用提供了运行环境。本文将介绍如何准备你的Java应用,并将其部署到Tomcat服务器上。 Java 应用部署 tomcat 的根目录结构 Tomcat中默认网站根目录是$CAT…

Java 学习笔记(基础篇2)

1. 分支结构① if 语句:(1) 双分支:if (条件) {// 语句体1 } else {// 语句体2 }(2) 多分支if (条件1) {// 语句体1 } else if (条件2) {// 语句体2 } else {// 语句体N }② switch 语句:(1) 语法:如果都不是(default&…