在 JavaScript 开发中,对象拷贝是一个绕不开的核心话题。无论是状态管理、数据缓存还是函数参数传递,我们都需要谨慎处理数据的复制方式,避免因引用共享导致意外的数据修改。
本文将结合实际开发场景,详细拆解浅拷贝与深拷贝的区别、实现方式及适用场景。
一、拷贝的本质:引用 vs 新对象
JavaScript中的对象(包括数组、函数等)属于引用类型,变量存储时存储的并非是对象本身,而是对象的引用地址。
- 原始类型拷贝:直接复制值,两个变量互不影响。
- 引用类型拷贝 :如果只是简单赋值(
const newObj = obj),本质是复制了对象的引用地址,新旧对象指向同一块内存,修改其中一个会直接影响另一个。
真正的 "拷贝",是基于原对象创建一个新对象 ,使新对象与原对象在内存上相互独立。根据拷贝的深度,又分为浅拷贝 和深拷贝。
二、浅拷贝:只复制第一层
浅拷贝(Shallow Copy)只会复制对象的第一层属性,如果属性值是引用类型(如子对象、数组),则仍然复制其引用地址。
核心特点
- 新对象的第一层属性与原对象隔离。
- 嵌套的子对象 / 数组仍共享引用,修改子对象会影响原对象。
常用实现方式
1. 数组专用方法
-
Array.prototype.slice(0):创建原数组的浅拷贝。iniconst arr = [1, 2, { a: 3 }]; const newArr = arr.slice(0); newArr[2].a = 4; // 会修改原数组的 arr[2].a -
扩展运算符
...:ES6 新增,语法更简洁。iniconst newArr = [...arr]; -
Array.prototype.concat():合并数组并返回新数组。iniconst newArr = [].concat(arr);
在一个空数组后拼接原数组并赋值给新数组,这个新数组就可以说是由原数组拷贝所得到的。
toReversed()与reverse()方法:
toReversed() 反转数组,得到一个新数组 ;reverse() 反转数组,改变原数组。通过这两个方法组合,我们就可以实现浅拷贝的效果。
ini
const newArr=arr.toReversed().reverse()
2. 对象通用方法
-
Object.assign({}, obj):将原对象的可枚举属性复制到新对象。iniconst obj = { a: 1, b: { c: 2 } }; const newObj = Object.assign({}, obj); newObj.b.c = 3; // 原对象 obj.b.c 也会变为 3 -
Object.assign():是 JavaScript 中用于对象属性复制与合并 的核心方法,它能将一个或多个源对象 的可枚举属性 复制到目标对象中,并返回修改后的目标对象。核心语法
css
Object.assign(target, ...sources)
target是接受属性的目标修改对象,...sources是一个或多个提供属性的对象。
代码示例:
ini
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }
若目标对象与源对象存在同名属性,后面的源对象属性会覆盖前面的。
三、深拷贝:彻底隔离数据
深拷贝(Deep Copy)会递归复制对象的所有层级,包括嵌套的子对象、数组等,最终得到一个与原对象完全独立的新对象,修改新对象不会对原对象产生任何影响。
核心特点
- 新对象与原对象在内存上完全隔离。
- 无论修改哪一层属性,都不会影响对方。
常用实现方式
1. JSON.parse(JSON.stringify(obj))
这是最常用的 "民间" 深拷贝方案,先将对象序列化为 JSON 字符串,再反序列化为新对象。
ini
const obj = { a: 1, b: { c: 2 } };
const newObj = JSON.parse(JSON.stringify(obj));
newObj.b.c = 3; // 原对象不受影响
局限性 :无法处理函数、Symbol、BigInt、undefined、NaN、Infinity、function 等特殊类型,且会丢失原型链。
2. structuredClone()
浏览器原生 API,现代浏览器和 Node.js 17+ 支持,是更标准的深拷贝方案。
ini
const newObj = structuredClone(obj);
局限性 :无法拷贝函数、Symbol,也不能处理带有循环引用的对象。
四、总结
- 浅拷贝:高效、轻量,适合处理扁平结构数据,但要注意嵌套引用的问题。
- 深拷贝:彻底隔离数据,避免副作用,但性能开销更大。
- 核心原则:根据数据结构和业务场景选择合适的拷贝方式,避免过度设计。
在实际开发中,我们应优先使用浅拷贝保证性能,只有在数据结构复杂且需要完全隔离时,才考虑深拷贝。理解拷贝的本质,是写出健壮、可维护的 JavaScript 代码的关键一步。