1. 深拷贝与浅拷贝的本质区别
在编程中,对象复制是一个看似简单却暗藏玄机的操作。很多开发者在使用对象复制时都曾踩过坑,特别是在处理嵌套对象或数组时。理解深拷贝和浅拷贝的区别,是避免这类问题的关键。
浅拷贝就像复印一张纸上的内容。如果纸上只有文字(基本类型),那么复印出来的内容和原件完全一样。但如果纸上还贴了便利贴(引用类型),复印出来的只是便利贴的位置信息,而不是便利贴本身的内容。修改原件上的便利贴内容,复印件上的便利贴也会跟着改变,因为它们指向的是同一个便利贴。
深拷贝则更像是把原件上的所有内容,包括便利贴上的信息都重新手写一份。这样修改新对象时,原对象完全不受影响。深拷贝会递归复制对象的所有层级,直到所有属性都是基本类型为止。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 内存模型解析
要真正理解深浅拷贝,我们需要先了解JavaScript的内存模型。JavaScript中的变量存储在栈内存中,而对象则存储在堆内存中。
当执行浅拷贝时:
- 基本类型:直接在栈内存中创建新值
- 引用类型:只在栈内存中创建新指针,指向同一个堆内存地址
而深拷贝则是:
- 基本类型:同样在栈内存中创建新值
- 引用类型:在堆内存中开辟新空间,递归复制所有层级的数据
这种内存分配方式的差异,直接导致了深浅拷贝在行为上的不同。
3. 浅拷贝的5种实现方式及陷阱
3.1 Object.assign()方法
Object.assign()是最常用的浅拷贝方法之一,它可以将一个或多个源对象的可枚举属性复制到目标对象。
javascript复制const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
original.b.c = 3;
console.log(copy.b.c); // 3,因为b是引用类型,修改会影响拷贝对象
注意:Object.assign()不会复制原型链上的属性和不可枚举属性。如果源对象有getter方法,它会在复制时执行getter而不是复制getter本身。
3.2 展开运算符(...)
ES6的展开运算符提供了一种更简洁的浅拷贝方式:
javascript复制const original = { x: 1, y: { z: 2 } };
const copy = { ...original };
original.y.z = 3;
console.log(copy.y.z); // 3
展开运算符在处理数组时同样有效:
javascript复制const arr = [1, 2, { a: 3 }];
const arrCopy = [...arr];
arr[2].a = 4;
console.log(arrCopy[2].a); // 4
3.3 Array.prototype.slice()
对于数组,slice方法可以实现浅拷贝:
javascript复制const arr = [1, 2, { a: 3 }];
const arrCopy = arr.slice();
arr[2].a = 4;
console.log(arrCopy[2].a); // 4
3.4 Array.prototype.concat()
concat方法也可以实现数组的浅拷贝:
javascript复制const arr = [1, 2, { a: 3 }];
const arrCopy = arr.concat();
arr[2].a = 4;
console.log(arrCopy[2].a); // 4
3.5 手动实现浅拷贝
理解浅拷贝的原理后,我们可以手动实现一个:
javascript复制function shallowCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
const copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key
