在 JavaScript 开发中,数据拷贝是常见需求,尤其是对象或数组的复制。理解浅拷贝和深拷贝的区别,能帮助我们避免常见错误。
一、浅拷贝
1.1 什么是浅拷贝?
浅拷贝创建一个新对象/数组,顶层属性值与原数据相同,但对于引用类型属性(如对象、数组),新数据与原数据共享内存引用。即:
- 基本类型(如
number
、string
):值复制。 - 引用类型(如
object
、array
):仅复制引用地址,而非实际对象。
1.2 浅拷贝的实现方式
(1) Object.assign()
将源对象的可枚举属性浅拷贝到目标对象,嵌套对象为引用共享。
let obj = { a: 1, b: { n: 2 } }; let copy = Object.assign({}, obj); obj.b.n = 200; console.log(copy.b.n); // 输出 200(共享引用)
(2) Object.create()
基于原型创建新对象,新对象的隐式原型为原对象。
let obj = { a: 1, b: { n: 2 } }; let copy = Object.create(obj); // 创建隐式原型为obj的空对象 copy.a = 10; // {a: 10} obj.b.n = 1; console.log(copy.a); // 输出 10(自身属性) console.log(copy.b); // 输出 { n: 1 }(引用共享)
(3) 展开运算符 ...
展开对象属性到新对象,嵌套对象为引用共享。
let obj = { a: 1, b: { n: 2 } }; let copy = { ...obj }; obj.b.n = 200; console.log(copy.b.n); // 输出 200
(4) 数组的 slice()
和 concat()
创建新数组,元素为浅拷贝,引用类型共享。
// slice() 示例 let arr = [1, 2, { n: 3 }]; let copy = arr.slice(); arr[2].n = 300; console.log(copy[2].n); // 输出 300(共享对象) // concat() 示例 let arr = [1, 2, 3, { a: 1 }]; let copy = arr.concat(); arr[3].a = 200; console.log(copy[3].a); // 输出 200(共享对象)
1.3 浅拷贝的风险
- 引用共享导致数据污染:修改拷贝对象的引用类型属性,会影响原对象。
- 隐式原型拷贝:可能复制对象的隐式原型(如通过
Object.create()
)。
二、深拷贝
2.1 什么是深拷贝?
递归拷贝对象的所有层级数据,包括嵌套的对象/数组,新对象与原对象完全独立,修改互不影响。
2.2 深拷贝的实现方式
(1) JSON.parse(JSON.stringify(obj))
通过 JSON 序列化/反序列化实现,简单但有诸多限制。
let obj = { a: 1, b: { n: 2 } }; let copy = JSON.parse(JSON.stringify(obj)); obj.b.n = 200; console.log(copy.b.n); // 输出 2(完全独立)
限制:
- 无法处理
undefined
、symbol
、bigint
、Date
、NaN
等特殊类型。 - 会丢失对象的原型链。
- 不能复制函数、正则表达式。
(2) 递归拷贝(手动实现)
通过递归遍历对象,逐层复制属性,处理引用类型。
function deepClone(obj) { if (typeof obj!== 'object' || obj === null) { return obj; // 基本类型或 null 直接返回 } // 判断是否为数组,创建对应类型的新对象 const newObj = Array.isArray(obj)? [] : {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = deepClone(obj[key]); // 递归拷贝子属性 } } return newObj; } let obj = { a: 1, b: { n: 2 } }; let copy = deepClone(obj); obj.b.n = 200; console.log(copy.b.n); // 输出 2(深拷贝成功)
(3) structuredClone()
(原生方法)
浏览器提供的原生深拷贝方法,支持复制 Map
、Set
、Date
、ArrayBuffer
等复杂类型。
let obj = { a: 1, b: { n: 2 } }; let copy = structuredClone(obj); obj.b.n = 200; console.log(copy.b.n); // 输出 2(深拷贝成功)
注意:兼容性较差,旧版浏览器可能不支持。
(4) MessageChannel
(消息通道实现)
通过消息传递实现深拷贝,适用于特定场景(一般不常用)。
function deepClone(obj) { return new Promise((resolve) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(obj); // 通过通道发送数据 port2.onmessage = (ev) => resolve(ev.data); // 接收拷贝后的数据 }); } let obj = { a: 1, b: { n: 2 } }; deepClone(obj).then((copy) => { obj.b.n = 200; console.log(copy.b.n); // 输出 2(深拷贝成功) });
2.3 深拷贝的优缺点
- 优点:新对象与原对象完全独立,修改无副作用。
- 缺点:
- 性能开销大(尤其嵌套层级深时)。
- 需要手动处理特殊数据类型(如函数、原型链)。
三、总结对比
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
复制层级 | 仅顶层属性,引用类型共享内存 | 递归复制所有层级,完全独立 |
适用场景 | 简单数据、性能优先 | 复杂对象、需要数据隔离 |
典型方法 | Object.assign() 、展开运算符 | 递归函数、JSON.stringify() |
风险 | 引用共享导致数据联动 | 性能损耗、特殊类型处理复杂 |
选择建议:
- 若只需复制基本类型或浅层引用,用浅拷贝(高效)。
- 若涉及多层嵌套对象且需完全独立,用深拷贝(牺牲性能换隔离)。
理解两者差异,能更精准地处理数据复制需求,避免因引用共享引发的潜在问题。