JS 合并数组( 5种方法 )

JS 合并数组(5种方法)

一、背景与问题

在JavaScript开发中,数组合并是一个基础但高频的操作。无论是处理用户输入数据、聚合API返回结果,还是进行数据预处理,合并数组都是核心操作之一。然而,由于JavaScript的数组类型是引用类型,合并过程中容易出现浅拷贝和引用污染问题,导致后续数据操作出现不可预期的副作用。

例如,假设我们有两个数组:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

如果直接使用arr1 + arr2,由于数组不能直接相加,必须通过其他方法实现合并。本文将深入分析5种常见的合并数组方法,探讨其工作原理、适用场景、性能差异和潜在陷阱。

二、基本原理

JavaScript的数组合并本质是创建一个新的数组,并将原数组的元素依次复制进去。由于数组是引用类型,浅拷贝的复制方式决定了合并结果是否包含原数组的引用。以下方法的差异主要体现在:

  1. 复制方式:是浅拷贝还是深拷贝
  2. 是否改变原数组:是否修改原数组的引用
  3. 性能开销:遍历次数、内存分配等

三、环境准备

确保开发环境支持ES6及以上标准,以下代码示例均在Node.js v18以上或现代浏览器中运行。

四、核心实现

1. 使用Array.prototype.concat()方法

这是最传统的数组合并方式,适用于浅拷贝合并。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = arr1.concat(arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]

原理:concat()方法会创建一个新数组,将原数组的元素依次复制进去。若传入数组参数,则会将数组元素展开复制。注意,它不会修改原数组。

关键点:

  • 深度合并需要递归处理嵌套数组
  • 可以合并多个数组:arr1.concat(arr2, arr3)

性能分析:时间复杂度O(n),空间复杂度O(n),适合中小型数组。

2. 使用展开运算符...(Spread Operator)

ES6引入的展开运算符,是当前最推荐的合并方式。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

原理:展开运算符会将数组的元素逐个复制到新数组中,实现浅拷贝。与concat()类似,但语法更简洁。

关键点:

  • 可以合并多个数组:[...arr1, ...arr2, ...arr3]
  • 适用于对象数组的浅合并(如合并用户数据)

性能分析:与concat()相当,但语法更灵活。

3. 使用Array.from()方法

适用于将类数组对象转换为数组并合并。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = Array.from(arr1, (value) => value).concat(arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]

原理:Array.from()会创建一个新数组,通过回调函数将原数组元素转换后再合并。适合处理非数组的类数组对象(如arguments对象)。

关键点:

  • 可以处理非数组的可迭代对象
  • 需要配合concat()使用实现合并

性能分析:额外的回调函数开销,适合需要转换的场景。

4. 使用apply()方法(ES5兼容)

适用于需要兼容旧版浏览器的场景。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = Array.prototype.slice.apply(arr1, arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]

原理:slice()方法会创建一个新数组,apply()会将数组作为参数传递。实际上等效于arr1.slice().concat(arr2)。

关键点:

  • 需要处理arguments对象时使用slice.apply(arguments)
  • 与concat()效果相同,但语法不同

性能分析:与concat()性能相当,但需要处理参数传递。

5. 使用reduce()方法(函数式编程)

适合需要链式操作的场景。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = arr2.reduce((acc, val) => [...acc, val], arr1);
console.log(merged); // [1, 2, 3, 4, 5, 6]

原理:reduce()通过累积器逐个合并元素,最终返回一个新数组。适合需要链式调用或处理复杂逻辑的场景。

关键点:

  • 可以处理非数组的可迭代对象
  • 需要初始化为第一个数组

性能分析:时间复杂度O(n),但需要额外的初始化步骤。

五、完整案例

案例:合并用户数据

假设我们需要合并两个用户列表,其中包含对象数组,且需要处理嵌套数组。

// 原始数据
const users1 = [
  { id: 1, name: 'Alice', tags: ['js', 'vue'] },
  { id: 2, name: 'Bob', tags: ['node', 'express'] }
];
const users2 = [
  { id: 3, name: 'Charlie', tags: ['python', 'django'] },
  { id: 4, name: 'David', tags: ['java', 'spring'] }
];

// 合并数组(浅拷贝)
const mergedUsers = [...users1, ...users2];

// 深度合并嵌套数组(处理tags)
const deepMergedUsers = users1
  .map(user => ({ ...user, tags: [...user.tags] }))
  .concat(users2.map(user => ({ ...user, tags: [...user.tags] })));

console.log(deepMergedUsers);

关键点:

  • 使用map()处理嵌套数组
  • 需要递归处理多层嵌套(如tags数组)
  • 避免引用污染

性能优化:对于大数据量,使用Array.from()或reduce()可能更高效。

六、源码解析

以Array.prototype.concat()为例,其核心逻辑如下(简化版):

Array.prototype.concat = function() {
  const result = new Array(this.length);
  for (let i = 0; i < this.length; i++) {
    result[i] = this[i];
  }
  
  for (let i = 0; i < arguments.length; i++) {
    if (Array.isArray(arguments[i])) {
      for (let j = 0; j < arguments[i].length; j++) {
        result[result.length] = arguments[i][j];
      }
    } else {
      result[result.length] = arguments[i];
    }
  }
  
  return result;
};

关键点:

  • 首先复制原数组元素
  • 然后处理传入的参数,如果是数组则展开复制
  • 返回新的数组实例

七、进阶使用

1. 合并对象数组

const arr1 = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const arr2 = [{ id: 3, name: 'Charlie' }];
const merged = [...arr1, ...arr2];
console.log(merged);

2. 合并嵌套数组

const arr1 = [[1, 2], [3, 4]];
const arr2 = [[5, 6], [7, 8]];
const merged = arr1.flatMap(arr => arr).concat(arr2.flatMap(arr => arr));
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8]

3. 响应式数据合并

在Vue中处理响应式数组:

import { reactive } from 'vue';

const state = reactive({
  list1: [1, 2],
  list2: [3, 4]
});

const merged = [...state.list1, ...state.list2];
console.log(merged);

八、性能与工程实践

1. 性能优化

  • 大数据量:使用Array.from()或reduce()可能更高效
  • 避免重复操作:合并后应避免再次修改原数组
  • 内存管理:频繁合并可能导致内存碎片,需注意回收

2. 异常处理

try {
  const merged = arr1.concat(arr2);
  if (!Array.isArray(merged)) {
    throw new TypeError('合并结果不是数组');
  }
} catch (e) {
  console.error('合并失败:', e.message);
}

3. 安全风险

  • 引用污染:合并对象数组可能导致数据污染
  • 数据完整性:合并时需确保数据来源可靠

九、常见问题与踩坑

1. 浅拷贝陷阱

const arr1 = [{ id: 1 }];
const arr2 = [arr1[0]];
const merged = [...arr1, ...arr2];
console.log(merged[0] === merged[1]); // true

问题:合并后的对象引用相同,修改其中一个会影响另一个。

2. 空值处理

const arr1 = [1, 2];
const arr2 = [];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2]

问题:空数组不会影响合并结果。

3. 非数组参数处理

const arr1 = [1, 2];
const merged = arr1.concat(3, 4);
console.log(merged); // [1, 2, 3, 4]

问题:非数组参数会被直接追加,而非作为数组处理。

十、最佳实践

1. 推荐方案

  • 常规合并:使用[...arr1, ...arr2]
  • 深度合并:使用map()处理嵌套结构
  • 兼容性需求:使用Array.prototype.slice.apply()处理arguments对象

2. 推荐场景

  • 数据聚合:合并用户数据、日志数据等
  • 数据预处理:在数据管道中合并多个数据源
  • 响应式框架:在Vue/React中处理响应式数组

3. 不推荐场景

  • 需要深拷贝:使用JSON.parse(JSON.stringify())替代
  • 处理大量数据:使用Array.from()或reduce()优化性能
  • 需要链式调用:使用reduce()或flatMap()处理

十一、总结

JavaScript数组合并是基础但关键的操作,不同的方法适用于不同场景。本文深入分析了concat()、展开运算符、Array.from()、apply()和reduce()五种方法的原理、优缺点和适用场景。通过实际案例和源码解析,帮助开发者理解不同方法的底层实现。在实际开发中,需要根据数据规模、性能需求和代码可维护性选择合适的合并方式,避免浅拷贝陷阱和引用污染问题。掌握这些技巧,可以显著提升代码的健壮性和可维护性。

最后修改于:2026年09月19日 06:05

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日