深入讲解在 JavaScript 中如何利用 ES6 扩展运算符(...)合并两个或多个数组,涵盖基础用法、任意位置插值、条件合并、浅拷贝机制及与 concat 的性能对比。
JS怎么使用扩展运算符合并多个数组
作者:函数茶馆
时间:2026-11-12
浏览:200
文章目录
核心结论:在 JavaScript(ES6+)中,合并多个数组最直接高效的方法是在新数组字面量中使用扩展运算符(
...),语法结构为 const merged = [...arr1, ...arr2, ...arr3];。该语法会按书写顺序依次提取各源数组中的每个元素,平铺放入一个全新创建的数组实例中。原数组保持只读且不产生任何副作用。
图 0:使用 ES6 扩展运算符合并多个源数组到新容器的整体概念图
一、基础语法:合并两个及多个数组的标准写法
扩展运算符由三个连续的英文半角句点(...)表示。当它放置在数组字面量 [] 内部并紧随一个数组变量时,作用是将该数组拆解为独立的逗号分隔值序列。
1. 合并两个数组
日常业务中最简单的双数组合并代码如下:
const frontEnd = ['HTML', 'CSS', 'JavaScript'];
const backEnd = ['Node.js', 'Python'];
// 使用扩展运算符解构并合并
const fullStack = [...frontEnd, ...backEnd];
console.log(fullStack);
// 输出: ['HTML', 'CSS', 'JavaScript', 'Node.js', 'Python']
2. 合并三个或更多数组
扩展运算符没有源数组数量限制,只需要在数组字面量中用逗号依次罗列即可:
const q1Sales = [120, 140];
const q2Sales = [180, 210];
const q3Sales = [160, 190];
const q4Sales = [230, 300];
const yearlySales = [...q1Sales, ...q2Sales, ...q3Sales, ...q4Sales];
console.log(yearlySales);
// 输出: [120, 140, 180, 210, 160, 190, 230, 300]
不可变性保证:执行
[...q1Sales, ...q2Sales] 后,q1Sales 与 q2Sales 的内部元素和长度均未发生变化。这种无副作用(Pure/Immutable)的特性契合现代前端框架(如 React、Vue 3)的状态更新要求。
图 1:扩展运算符展开解构与合并新数组的底层 4 个执行阶段
二、进阶技巧:任意位置插值与条件合并
相比于传统的 concat() 方法,扩展运算符最大的优势在于它依托于数组字面量 [],能够以高度直观的方式自由编排元素顺序与插入固定项。
1. 在合并过程中插入新项
你可以在数组的首部、中间或尾部直接添加独立的值:
const users = ['Alice', 'Bob'];
const admins = ['Root'];
// 在开头插入系统项,在中间插入分隔项,在结尾追加访客项
const allRoles = ['SuperAdmin', ...admins, 'Divider', ...users, 'Guest'];
console.log(allRoles);
// 输出: ['SuperAdmin', 'Root', 'Divider', 'Alice', 'Bob', 'Guest']
2. 结合三元运算符实现条件合并
实际开发中经常需要根据权限或开关决定是否合并某组数组。可以利用空数组展开为空的特性:
const basicNav = ['首页', '产品列表'];
const vipNav = ['专属折扣', '客服通道'];
const isVipUser = true;
// 当条件为 false 时,展开 [] 不会向新数组添加任何元素,也不会留下 undefined
const currentMenu = [
...basicNav,
...(isVipUser ? vipNav : [])
];
console.log(currentMenu);
// 输出: ['首页', '产品列表', '专属折扣', '客服通道']
图 2:数组字面量中单项、多数组展开与条件逻辑的槽位组织结构
三、跨类型合并:合并 Set、Map 及类数组对象
扩展运算符基于 JavaScript 的迭代协议(Iteration Protocols)工作。这意味着它不仅能合并普通数组,还能同时合并任何实现了 [Symbol.iterator] 接口的对象。
1. 数组去重后合并(结合 Set)
当合并的多个数组中包含重复数据时,可以通过 Set 高效完成去重并恢复为数组:
const listA = [1, 2, 3];
const listB = [2, 3, 4, 5];
// 构造 Set 自动过滤重复值,再用扩展运算符展开还原为数组
const uniqueMerged = [...new Set([...listA, ...listB])];
console.log(uniqueMerged);
// 输出: [1, 2, 3, 4, 5]
2. 合并 DOM NodeList 或 arguments
在浏览器环境中,document.querySelectorAll() 返回的是类数组对象 NodeList。扩展运算符可以直接将其展开并与常规数组无缝融合:
const fixedElements = [headerElement];
const dynamicCards = document.querySelectorAll('.card-item');
// dynamicCards 是 NodeList,扩展运算符直接解构
const allDomNodes = [...fixedElements, ...dynamicCards];
四、核心注意事项:浅拷贝与对象引用的连带风险
扩展运算符合并数组属于浅拷贝(Shallow Copy)。理解这一本质是避免线上偶发 Bug 的关键。
- 基本类型元素(数值、字符串、布尔值):按值复制,在新数组中占用全新的内存地址,彼此完全独立。
- 引用类型元素(对象、嵌套数组、函数):仅复制对象在堆内存中的引用地址(指针)。合并后的新数组与原数组指向同一个物理对象。
图 3:浅拷贝机制下基础数据类型与引用类型的内存分配与连带影响
以下代码直观展示了这一连带影响:
const teamA = [ { id: 101, name: 'Alice' } ];
const teamB = [ { id: 102, name: 'Bob' } ];
const allStaff = [...teamA, ...teamB];
// 修改合并数组中的对象属性
allStaff[0].name = 'Alicia';
console.log(teamA[0].name);
// 输出: 'Alicia' —— 原数组中的数据同样被修改!
避坑方案:如果数组中包含对象且要求完全隔离,合并时需进行深拷贝。现代浏览器与 Node.js 17+ 原生支持
structuredClone():const safeMerged = structuredClone([...teamA, ...teamB]);
五、异常处理与防御性代码规范
直接对 null 或 undefined 变量使用扩展运算符会直接抛出运行时错误:
const validArray = [1, 2];
let nullableData = null;
// 错误示范:抛出 TypeError: nullableData is not iterable
// const broken = [...validArray, ...nullableData];
// 正确防御方案:提供空数组默认回退
const safeResult = [...validArray, ...(nullableData || [])];
console.log(safeResult); // 正常输出: [1, 2]
六、与其他合并方式横向对比与技术选型
JavaScript 提供了多种合并数组的途径。以下是扩展运算符与传统方法的关键特性比对:
图 4:扩展运算符、concat 与 push.apply 的多维对比与决策树
| 合并方式 | 是否修改原数组 | 可读性与灵活性 | 非数组可迭代对象支持 | 典型适用场景 |
|---|---|---|---|---|
[...a, ...b] |
否(生成新数组) | 最高(支持任意位置插值) | 原生支持(Set/NodeList/Map等) | 绝大多数现代日常开发首选 |
a.concat(b) |
否(生成新数组) | 一般(仅支持末尾拼接) | 需手动先转为数组 | 需要向下兼容 ES5 运行时的项目 |
a.push(...b) |
是(原地修改原数组 a) | 中等 | 原生支持展开 | 内存极其受限、需要复用原数组句柄 |
超大数组内存提示:当需要合并单次超过 10 万条记录的超大型数组时,由于
push(...largeArray) 会将展开元素作为函数调用实参压入栈,可能触碰 JavaScript 引擎的最大调用栈上限(Maximum call stack size exceeded)。而使用字面量 [...a, ...b] 或原生 a.concat(b) 则不受函数实参调用栈制约,在海量数据拼接中表现更为稳固。
七、快速检查清单
- 语法书写:确保三个点紧随源数组名称,且整体包裹在目标
[]字面量中。 - 防御检查:若源数组可能为
null或undefined,务必使用(arr || [])兜底。 - 引用深度:如果合并项包含对象,评估是否需要使用
structuredClone进行深层解耦。 - 业务意图:如需保持原数据不变选用
[...a, ...b];若确定要改变原数据以节约内存开销才选用a.push(...b)。
实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。
作者最新文章
相关文章