深拷贝浅拷贝,面试可不能再不会了
它们的区别主要体现在对于对象(或数组)内部元素的复制方式上,包括对原始数据类型和引用类型数据的处理。
浅拷贝
浅拷贝只会复制对象(或数组)的第一层属性,如果属性值本身还是一个对象(或数组),那么它只复制引用,原始对象和复制对象会共享这个属性。修改其中一个对象的这个属性值,另一个对象的该属性值也会发生变化。
let obj1 = { a: 1, b: [1, 2, 3] };
let obj2 = Object.assign({}, obj1);
obj2.b.push(4);
console.log(obj1.b); // 输出 [1, 2, 3, 4],因为 obj1.b 和 obj2.b 指向的是同一个数组
深拷贝
深拷贝则不同,它会递归复制对象的所有层级属性。
也就是说,如果对象的某个属性值本身是一个对象或者数组,它会继续为这个子对象创建一个新的对象,复制其属性,而不是简单复制引用。因此,深拷贝后的对象修改属性值,原始对象的该属性值不会改变。
let obj1 = { a: 1, b: [1, 2, 3] };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
console.log(obj1.b); // 输出 [1, 2, 3],因为 obj1.b 和 obj2.b 不再是同一个数组
但是要注意,JSON.stringify 和 JSON.parse 方法存在一些局限性,例如无法复制函数,无法复制 Symbol 类型的值,无法复制循环引用的对象等。
对于这种情况,我们需要采用其他方式实现深拷贝,比如使用递归方式。
实现深拷贝的示例:递归
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
使用这个函数进行深拷贝
let obj1 = { a: 1, b: [1, 2, 3], c: { d: 4 } };
let obj2 = deepCopy(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1.b); // 输出 [1, 2, 3]
console.log(obj1.c.d); // 输出 4
需要注意的是:
1、循环引用处理
想象你正在复制一本书,但在第10页上有一条注释说“参见第10页”。如果你照单全收,你就会不停地复制第10页,这就是循环引用。在实际编程中,这种情况可能出现在一个对象的属性间接或直接引用了对象自身。所以,在深拷贝时,我们需要设置检测机制防止无限循环的发生。
2、对特殊对象的处理
某些特殊的对象类型,如日期(Date)、正则表达式(RegExp)等,如果直接复制,可能会丢失他们特有的属性或方法。就像你复制一个蓝色的苹果,结果复制出来的却是一个没有颜色的苹果。因此,在深拷贝时,对于这类特殊对象我们需要做特别处理。
3、对原型链的处理
在 JavaScript 中,对象都有原型链,这就像是一个家谱,用于继承属性和方法。在深拷贝时,我们需要决定是否也复制这个“家谱”。
这就好比你在复制一本书时,是否也需要复制出版社的信息。
4、性能优化
深拷贝可能涉及大量的复制操作,如何在保证正确性的同时进行性能优化,是一个需要掌握的技能。
就像你在复印一本书时,需要找出最高效的复印策略,避免反复复印同一张纸。
在实际工作中,如果你需要复制一个对象,而这个对象可能有上述提到的一些复杂情况,这时你就需要了解并应用这些深拷贝的知识。
同时,一些成熟的工具库,如 lodash,已经提供了处理这些复杂情况的深拷贝方法,我们可以直接使用,避免重复造轮子。
转载自:https://juejin.cn/post/7236010330039582778