JavaScript的深拷贝和浅拷贝
JavaScript 中分两种数据类型,一种是基础数据类型,另一种是引用数据类型,深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用数据类型的。
赋值和浅拷贝的区别
- 由于引用数据类型是存放在堆内存中的,对一个对象的赋值,实际上赋的是该对象的在栈中的地址,而不是堆中的数据。因此这两个对象指向的是同一个堆内的存储空间,无论哪个对象发生改变,存储空间的内容都会相应发生改变,两个对象是联动的。
- 浅拷贝创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。浅拷贝其实就是解决了单层的赋值问题。
浅拷贝的实现方式(当拷贝对象只有一层的时候,就是深拷贝)
...
展开运算符- Object.assign()
- Array.prototype.slice()
slice()
方法返回一个新的数组对象,这一对象是一个由 begin 和 end(不包括 end)决定的原数组的浅拷贝。原始数组不会被改变。
- Array.prototype.concat()
深拷贝,拷贝之后两个对象相互独立,互不影响
深拷贝的实现方式
1. JSON.parse(JSON.stringify(Object))
let a = [0, "1", [2, 3]];
let b = JSON.parse(JSON.stringify(a.slice(1)));
console.log(b);
// ["1", [2, 3]]
缺点:
undefined
、symbol
和函数
这三种情况会直接忽略- 不能序列化函数
- 不能解决循环引用的对象
- 不能正确处理 new Date()
2. 递归实现
function deepClone(target) {
if (typeof target === 'object') {
// 兼容数组
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = deepClone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
3. 解决递归深拷贝的循环引用问题
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child',
},
field4: [2, 4, 8],
};
target.target = target;
对象的属性间接或直接的引用了自身的情况的情况下,该方法会进入死循环导致栈内存溢出
// 解决循环引用的递归
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
// 额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = deepClone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};