JavaScript基础之深拷贝浅拷贝
什么是深拷贝,什么是浅拷贝?
浅拷贝
定义:
创建一个新的数组或者对象,并将原数组或者对象的顶层属性逐一复制到新创建的对象或者数组中。
特点:
- 对于基本数据类型,直接复制其值。
- 对于引用类型(对象,数组,方法),仅仅复制该引用。
示例:
// 浅拷贝示例
let origin = { a: 1, nested: { b: 2 } };
let copy = Object.assign({}, origin);
copy.nested.b = 3;
console.log(origin.nested.b); //3
copy.a = 4;
console.log(origin.a); //1
在这个示例中,我们修改了拷贝的新对象的nested对象的b属性的值,原始对象的nested对象的b属性值也跟着发生了改变。而修改拷贝对象属性a的值,原始对象的属性a的值没有跟着改变。
实现方法:
1.使用Object.assign方法
这是js给我们提供的浅拷贝方法。
let newObj = Object.assign({}, oldObj);
2.使用扩展运算符
let newObj = {...oldObj};
3.手动实现浅拷贝函数
function shallowCopy(obj) {
let clone = {};
for (let key in obj) {
//这个判断是为了避免拷贝到原型链上的属性
//继承过来的属性可能存在风险,影响性能和安全
if (obj.hasOwnProperty(key)) {
clone[key] = obj[key];
}
}
return clone;
}
const copyObj = shallowCopy({ a: 1, nested: { b: 2 } });
console.log(copyObj);
这里需要说明的是obj.hasOwnProperty(key)这个判断是有必要加的,不加的话,在原型对象被修改的时候,修改的属性也会被继承,虽然也能实现浅拷贝功能,但主要的问题是我们不知道原型属性怎么被修改,存在不可控性,继承过来的属性可能存在风险,影响性能和安全。
深拷贝
定义:
不仅仅复制第一层的属性,并且会递归的复制子对象及其后代的属性,从而生成一个完全独立的新对象。
特点:
- 修改拷贝对象不会影响原对象,哪怕是修改子对象的任何嵌套属性。拷贝对象和原对象完全独立。
示例:
const originObj = { a: 1, nested: { b: 2 } };
const newDeepObj = JSON.parse(JSON.stringify(originObj));
newDeepObj.nested.b = 3;
console.log(newDeepObj); //{ a: 1, nested: { b: 3 } }
console.log(originObj); //{ a: 1, nested: { b: 2 } }
因为是深拷贝,及时新对象的嵌套引用类型属性被修改了,原对象对应的属性也没有跟着改变,两者互不影响。
注意事项:
这种实现方式存在一定的局限性。要想实现完美的深拷贝,需要手动实现深拷贝函数。
常见实现方式:
1.通过JSON.stringify()和JSON.parse()
如前面所示,存在局限性。
2.通过递归实现深拷贝
function deepCopy(obj) {
//如果是null或者基本数据类型直接返回。(在js中,typeof对象和数组都返回"object")
if (obj === null || typeof obj !== "object") {
return obj;
}
//经过第一个判断,现在obj只可能是数组或者对象
//如果是数组
if (Array.isArray(obj)) {
let copy = [];
for (let i in obj) {
copy[i] = deepCopy(obj[i]);
}
return copy;
}
let copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
let origin = { a: 1, nested: { b: 2 }, arr: [1, 2, 3] };
let copy = deepCopy(origin);
console.log(copy);
上面代码中的两个for循环代码是一样的,所以可以简化成下面的代码。虽然比较难以理解,但代码量更少。
function deepCopy(obj) {
//如果是null或者基本数据类型直接返回。(在js中,typeof对象和数组都返回"object")
if (obj === null || typeof obj !== "object") {
return obj;
}
//经过第一个判断,现在obj只可能是数组或者对象
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
let origin = { a: 1, nested: { b: 2 }, arr: [1, 2, 3] };
let copy = deepCopy(origin);
console.log(copy);
问题:浅拷贝修改内容会影响原对象,这是否存在风险?
是的,是会存在风险。所以浅拷贝的使用是有限制的。当然,就是用在需要修改原对象的场景。使用浅拷贝的好处是效率更高,不需要递归遍历子对象。同时浅拷贝的有下面的优点。
性能优化
- 速度更快: 浅拷贝只需要复制对象的第一层属性,而不必递归地复制整个对象树,因此执行速度快。
- 内存消耗低: 由于只需分配少量额外的空间来存储新对象的基本结构,而不是完整的内容,内存占用较低。
简化代码
- 在简单的数据结构中,浅拷贝可以使代码更加简洁易读。
- 适合那些不需要深层次分离的对象。
共享不可变数据
- 在某些设计模式中,允许多个部分共享同一份不变的数据是有益的。浅拷贝在这种情况下非常有用。
总结
浅拷贝 | 深拷贝 | |
---|---|---|
复制层次 | 第一层 | 全部层级 |
引用关系 | 新旧对象共用部分引用 | 彼此独立无关联 |
应用场景 | 数据不需要隔离的情况下 | 当你需要保证操作某个对象时不干扰其他相关联的对象 |