JavaScript中十种一步拷贝数组的方法实例详解
本文详细梳理了JavaScript中实现数组拷贝的十种常用方法,涵盖从ES6扩展运算符到传统循环、高阶函数及JSON序列化的多种技术。重点解析了浅拷贝与深拷贝的区别,通过代码示例说明各方法的适用场景及局限性,特别是针对嵌套对象和特殊数据类型(如时间、正则、函数)的拷贝陷阱与解决方案。
JavaScript数组拷贝概述
在JavaScript开发中,数组拷贝是一个高频需求。面对多样化的实现方式,系统梳理各种拷贝手段有助于开发者根据具体场景选择最优解。

扩展运算符与循环实现浅拷贝
1、扩展运算符(浅拷贝)
自ES6标准引入以来,这种方法已成为最主流的拷贝方式。其语法简洁直观,在React、Redux等现代前端库的使用场景中,因其不可变性要求而显得尤为关键。
numbers = [1, 2, 3];
numbersCopy = [...numbers];
这个方法不能有效的拷贝多维数组。数组/对象值的拷贝是通过引用而不是值复制。
// numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// 只修改了我们希望修改的,原数组不受影响
// nestedNumbers = [[1], [2]];
numbersCopy = [...nestedNumbers];
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 由于公用引用,所以两个数组都被修改了,这是我们不希望的
2、for()循环(浅拷贝)
尽管函数式编程范式日益普及,但传统的for循环方式因其命令式风格,目前看来可能不是最受推崇的选择。
numbers = [1, 2, 3];
numbersCopy = [];
for (i = 0; i < numbers.length; i++) {
numbersCopy[i] = numbers[i];
}
这个方法不能有效的拷贝多维数组。因为我们使用的是=运算符,它在处理数组/对象值的拷贝时通过引用而不是值复制。
// numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// nestedNumbers = [[1], [2]];
numbersCopy = [];
for (i = 0; i < nestedNumbers.length; i++) {
numbersCopy[i] = nestedNumbers[i];
}
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 由于公用引用,所以两个数组都被修改了,这是我们不希望的
3、while()循环(浅拷贝):其逻辑与for()循环类似,均属于基础控制流手段。
numbers = [1, 2, 3];
numbersCopy = [];
i = -1;
while (++i < numbers.length) {
numbersCopy[i] = numbers[i];
}
利用高阶函数进行数组拷贝
4、Array.map(浅拷贝)
相较于前述较为传统的循环方式,map方法更符合现代JavaScript编程习惯。map源自数学概念,旨在将一种集合转换为另一种集合,同时保持原有结构不变。
从语义上讲,Array.map确保返回的数组长度与原数组一致。
numbers = [1, 2, 3];
double = (x) => x * 2;
numbers.map(double);
使用map时,需传入一个回调函数处理当前数组元素,并返回新数组的对应元素。
这与数组拷贝有何关联?
若目标是复制数组,只需在map的回调函数中直接返回原元素即可实现拷贝。
numbers = [1, 2, 3];
numbersCopy = numbers.map((x) => x);
若从数学角度理解,(x) => x被称为恒等函数,它直接返回传入的任何参数。
identity = (x) => x;
numbers.map(identity);
// [1, 2, 3]
需注意,当数组元素为对象或嵌套数组时,map仅复制引用而非值,因此属于浅拷贝。
5、Array.filter(浅拷贝)
filter方法同样返回新数组,但其长度取决于过滤条件,通常用于筛选而非完整拷贝。
[1, 2, 3].filter((x) => x % 2 === 0)
// [2]
然而,若将过滤条件始终设为true,即可利用filter实现数组拷贝。
numbers = [1, 2, 3];
numbersCopy = numbers.filter(() => true);
// [1, 2, 3]
同样地,对于对象或嵌套数组,该方法仅复制引用,属于浅拷贝。
6、Array.reduce(浅拷贝)
虽然使用reduce来拷贝数组并未完全体现其聚合功能,但探讨其拷贝能力仍有意义。
numbers = [1, 2, 3];
numbersCopy = numbers.reduce((newArray, element) => {
newArray.push(element);
return newArray;
}, []);
reduce()方法通过提供的reducer函数遍历数组,将结果累加为单个返回值。
在示例中,初始值设为空数组,遍历原数组时将元素推入该空数组,并必须在每次迭代中返回累加器,以便后续迭代使用。
同理,处理对象或数组时仅复制引用,属于浅拷贝。
slice与JSON序列化实现深拷贝
7、Array.slice(浅拷贝)
slice方法根据指定的start和end索引,从原数组中提取子集并返回一个浅拷贝数组。
[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Starts at index 0, stops at index 3
// 当不给定参数时,就返回了原数组的拷贝
numbers = [1, 2, 3, 4, 5];
numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]
同样,若元素为对象或数组,仅复制引用,属于浅拷贝。
8、JSON.parse & JSON.stringify(深拷贝)
JSON.stringify将对象序列化为字符串; JSON.parse则将字符串反序列化为对象。
将两者结合,先将对象转为字符串,再转回对象,从而创建一个全新的数据结构。
nestedNumbers = [[1], [2]];
numbersCopy = JSON.parse(
JSON.stringify(nestedNumbers)
);
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1], [2]]
// [[1, 300], [2]]
// These two arrays are completely separate!
这种方法可以安全地拷贝深度嵌套的对象或数组,实现深拷贝效果。
几种特殊情况
1、若对象包含时间对象,经JSON.stringify后再JSON.parse,时间对象将变为字符串格式,而非Date对象;
var test = {
name: 'a',
date: [new Date(1536627600000), new Date(1540047600000)],
};
let b;
b = JSON.parse(JSON.stringify(test))
console.log(b)
2、若对象包含RegExp或Error对象,序列化后的结果将仅得到空对象;
const test = {
name: 'a',
date: new RegExp('\\w+'),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.log('ddd', test, copyed)
3、若对象包含函数或undefined值,序列化过程会导致这些属性丢失;
const test = {
name: 'a',
date: function hehe() {
console.log('fff')
},
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
4、若对象包含NaN、Infinity或-Infinity,序列化后的结果将变为null;
5、JSON.stringify()仅序列化对象的可枚举自有属性。例如,若对象由构造函数生成,使用JSON.parse(JSON.stringify(obj))进行深拷贝后,对象的constructor属性将会丢失;
function Person(name) {
this.name = name;
console.log(name)
}
const liai = new Person('liai');
const test = {
name: 'a',
date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
concat与from方法的浅拷贝应用
9、Array.concat(浅拷贝)
concat方法用于将数组与值或其他数组连接组合。
[1, 2, 3].concat(4); // [1, 2, 3, 4]
[1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]
若不指定参数或传入空数组作为参数,即可实现数组的浅拷贝。
[1, 2, 3].concat(); // [1, 2, 3]
[1, 2, 3].concat([]); // [1, 2, 3]
同样,处理对象或数组时仅复制引用,属于浅拷贝。
10、Array.from(浅拷贝)
该方法可将任何可迭代对象转换为数组,并返回原数组的浅拷贝。
console.log(Array.from('foo'))
// ['f', 'o', 'o']
numbers = [1, 2, 3];
numbersCopy = Array.from(numbers)
// [1, 2, 3]同样,处理对象或数组时仅复制引用,属于浅拷贝。
总结与展望
小结
上述方法均能在单步操作中完成拷贝。若结合其他技术或工具函数,还可发现更多数组拷贝的实现方式。
以上介绍了JavaScript中十种一步拷贝数组的方法实例详解,希望能为开发者提供帮助。如有疑问,欢迎留言,小编将及时回复!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
