前言
深拷贝和浅拷贝是处理对象复制时的两种不同方式。在进入正题前,我们先来简单回顾一下数据类型和常见的数据结构:堆和栈
基本数据类型
:Null、Undefined、Number、String、Boolean、Symbol、BigInt
引用数据类型
:Function、Array、RegExp、Math、Date、Error、Set、Map等
基本类型值保存在栈中,引用类型值保存在堆中,在栈中保留对象的引用地址
,当 JavaScript 访问数据时,通过栈中的引用地址进行访问。故在 JavaScript 中原始类型的赋值会完整复制变量值,而引用类型的赋值则是复制的引用地址
。
浅拷贝 (Shallow Copy)
浅拷贝只复制对象的第一层属性。如果对象的属性值是基本数据类型(如String、Number、Boolean等),则直接复制值;
如果属性值是引用类型(如Array、Object等),则复制其内存地址,而不是复制实际的值或嵌套的对象。以下都是浅拷贝的实现:
- Object.assign()
js
let origin = { a: '原始字符串', b: { c: 1 } };
let copy = Object.assign({}, origin);
origin.a = '改变后的字符串';
copy.b.c = 2;
console.log(origin); // {a: '改变后的字符串', b: {c: 2}}
console.log(copy); // {a: '原始字符串', b: {c: 2}}
- 扩展运算符
js
let origin = { a: '原始字符串', b: { c: 1 } };
let copy = { ...origin };
origin.a = '改变后的字符串';
copy.b.c = 2;
console.log(origin); // {a: '改变后的字符串', b: {c: 2}}
console.log(copy); // {a: '原始字符串', b: {c: 2}}
- slice
js
let origin = [1, 2, 3, 4];
let copy = origin.slice();
console.log(copy);
- concat
js
let original = [1, 2, 3, 4];
let copy = original.concat();
深拷贝 (Deep Copy)
深拷贝相对于浅拷贝来说,不仅复制了对象本身及其包含的原始类型的值,还复制了所有引用类型的实际值。
这意味着,如果你修改拷贝对象中的一个引用类型的值,原始对象中相应的值不会发生变化,因为它们指向了不同的内存地址。
深拷贝不仅复制对象的第一层属性,还递归复制所有的嵌套对象。这意味着,无论对象有多少层嵌套,深拷贝都会创建所有层次的副本。因此,原始对象和拷贝对象之间不会相互影响。
JSON.parse(JSON.stringify(object))
js
let original = { a: 1, b: { c: 2 } };
let copy = JSON.parse(JSON.stringify(original));
console.log(copy); // { a: 1, b: { c: 2 } }
JSON.parse(JSON.stringify(NaN)) // null
使用 JSON.stringify() 将对象序列化(转换为JSON字符串),然后使用 JSON.parse() 将字符串解析为新的对象。这种方法不能复制函数和循环引用的对象。
递归拷贝
实现深拷贝的一种方法是递归地复制所有属性。对于每个属性,你可以检查它是否是一个对象,如果是,你再次递归拷贝这个对象;如果不是,直接拷贝其值。
javascript
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy;
if (Array.isArray(obj)) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]);
}
} else {
copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 检查是否是对象自身具有的属性,因为in会包含原型链上的属性,这里只拷贝自身的!
copy[key] = deepCopy(obj[key]);
}
}
}
return copy;
}
这种方法递归地复制对象,但是对于特殊情况(如函数、Date、RegExp、循环引用等)需要特殊处理。
使用第三方库
:如Lodash的 _.cloneDeep()
js
let original = { a: 1, b: { c: 2 } };
let copy = _.cloneDeep(original);
Lodash 的 cloneDeep 方法是一个非常流行的深拷贝实现,它能够处理各种类型的值,并且能管理循环引用。
在选择方法时,需要根据具体需求来决定。如果你需要快速而简单地复制数据,而且不包含复杂对象(如函数、日期、正则表达式等),JSON的方法可能是最快的。
但如果需要一个更稳健和全面的解决方案,那么递归方法或第三方库会更加适用。
总结:
浅拷贝只复制对象的第一层属性,对于属性值是引用类型的,复制的是引用(内存地址)。
深拷贝递归复制所有层次的属性,创建一个完全独立的副本,原对象和拷贝对象之间不会相互影响。