深拷贝和浅拷贝

2021-03-02  本文已影响0人  呆桃冲鸭冲鸭

浅拷贝是自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

image.png

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

浅拷贝:
方法一:object.assign
方法二:扩展运算符方式
方法三:concat 拷贝数组
方法四:slice 拷贝数组
方法五:函数库lodash的_.clone方法

赋值和深/浅拷贝的区别
这三者的区别如下,不过比较的前提都是针对引用类型:

赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。


image.png

实现浅拷贝,大致的思路分为两点:
1.对基础类型做一个最基本的一个拷贝;
2.对引用类型开辟一个新的存储,并且拷贝一层对象属性。

//手动实现浅拷贝
// 1.对基础类型做一个最基本的一个拷贝;
// 2.对引用类型开辟一个新的存储,并且拷贝一层对象属性。
function cloneFn(val) {  
    if(typeof val === "object" && typeof val !== null){
        // 引用类型
        const target = Array.isArray(val) ? [] : {}
        for(var key in val){
            if(val.hasOwnProperty(key)){
                target[key] = val[key]
            }
        }
        return target
    }else{
        //基础类型
        return target
    }
}
console.log(cloneFn({
    name : '浪里行舟',
    arr : [1,[2,3],4],
}))

浅拷贝只是创建了一个新的对象,复制了原有对象的基本类型的值,而引用数据类型只拷贝了一层属性,再深层的还是无法进行拷贝。深拷贝则不同,对于复杂引用数据类型,其在堆内存中完全开辟了一块内存地址,并将原有的对象完全复制过来存放。
这两个对象是相互独立、不受影响的,彻底实现了内存上的分离。

深拷贝的原理:
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

深拷贝:
方法一:JSON.stringfy
把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象。
使用 JSON.stringfy 实现深拷贝的注意点:
拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
拷贝 Date 引用类型会变成字符串;
无法拷贝不可枚举的属性;
无法拷贝对象的原型链;
拷贝 RegExp 引用类型会变成空对象;
对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。

方法二:手写递归实现

let obj1 = {
    a:{  b:1 }
};
function deepClone(obj) { 
    let cloneObj = {}
    for(let key in obj) {                 
        if(typeof obj[key] ==='object') { 
            //对象就再次调用该函数递归
            cloneObj[key] = deepClone(obj[key])  
        } else {
            //基本类型的话直接复制值
            cloneObj[key] = obj[key]  
        }
    }
    return cloneObj
}

let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   //  {a:{b:1}}

存在问题:
1.这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
2.这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
3.对象的属性里面成环,即循环引用没有解决。

方法三:改进后递归实现

上一篇下一篇

猜你喜欢

热点阅读