javascript-浅拷贝和深拷贝

loading 2022年11月20日 165次浏览

1. 概念

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

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

        const a1 = { b: { c: {} } };

        var a2 = shallowClone(a1); // 浅拷贝方法
        a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存

        var a3 = deepClone(a3); // 深拷贝方法
        a3.b.c === a1.b.c // false 新对象跟原对象不共享内存

总之,浅拷贝前后对象的基本数据类型互不影响,引用数据类型由于共享内存互相影响。

深拷贝会直接在堆中创建一块新的内存,拷贝后两个对象完全互不影响。

2. 浅拷贝实现方式

2.1 Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

        let obj1 = { person: { name: "kobe", age: 41 }, sports: 'basketball' };
        let obj2 = Object.assign({}, obj1);

        obj2.person.name = "wade";
        obj2.sports = 'football'
        
        console.log(obj1); 
        // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

2.2 展开运算符...

es6里常用的新特性

        let obj1 = { name: 'Kobe', address: { x: 100, y: 100 } }
        let obj2 = { ...obj1 }

        obj1.address.x = 200;
        obj1.name = 'wade'
        
        console.log('obj2', obj2)
        // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }

2.3 Array.prototype.concat()

concat()不传参也能实现浅拷贝

        let arr = [1, 3, {username : 'kobe'}];
        let arr2 = arr.concat();
        arr2[2].username = 'wade';
        console.log(arr); //[ 1, 3, { username: 'wade' } ]

2.4 Array.prototype.slice()

和concat()差不多意思

        let arr = [1, 3, {username : 'kobe'}];
        let arr2 = arr.slice();
        arr2[2].username = 'wade';
        console.log(arr); //[ 1, 3, { username: 'wade' } ]

2.5 函数库lodash自带._clone方法

        var _ = require('lodash');
        var obj1 = {
            a: 1,
            b: { f: { g: 1 } },
            c: [1, 2, 3]
        };
        var obj2 = _.clone(obj1);
        console.log(obj1.b.f === obj2.b.f);// true

3. 深拷贝实现方式

3.1 JSON.parse(JSON.stringify())

首先利用JSON.stringify()将对象转换为JSON字符串,再用JSON.parse()将字符串解析成对象。这样就相当于创建了一个新的对象,而且新对象会在栈中开辟新的地址。

        let arr = [1, 3, {username : 'kobe'}];
        let arr2 = JSON.parse(JSON.stringify(arr));
        arr2[2].username = 'wade';
        console.log(arr); //[ 1, 3, { username: 'kobe' } ]

但是这种方法有它的弊端:

        let person = {
            name: "张三",
            hobbies: ["吃饭", "睡觉", "打豆豆"],
            date: new Date,
            fuc: () => { },
            reg: /w/
        }

        const person1 = JSON.parse(JSON.stringify(person));
        person1.name = '李四'
        person1.hobbies[0] = '打球'

        console.log(person);
	//结果有点长,复制运行一下看结果

结果中date属性从一个对象变成了一个字符串,fuc属性消失了,reg属性变成了空对象。所以说这种方式也是有弊端的:

  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
  • Date对象会被当作字符串处理
  • NaN和Infinity会被当作null
  • Map,Set等对象中只能序列化可枚举的属性
  • 对包含循环引用的对象执行会报错

因此最好还是用手写的递归浅拷贝方法。

3.2 函数库lodash自带.cloneDeep方法

        var _ = require('lodash');
        var obj1 = {
            a: 1,
            b: { f: { g: 1 } },
            c: [1, 2, 3]
        };
        var obj2 = _.cloneDeep(obj1);
        console.log(obj1.b.f === obj2.b.f);// false

4.手写深拷贝和浅拷贝

4.1 手写浅拷贝

浅拷贝的手写还是比较好理解的,创建个空对象用来克隆,通过for...in遍历obj的键,如果obj自身有这个属性就复制过去。

注意for...in循环会把原型上的属性也给遍历了,所以如果用for...in要加hasOwnProperty()

用new obj.constructor()是为了包含obj是数组/函数等情况

        const shallowCopy = (obj) =>{
            let res = new obj.constructor();
            for(let i of Object.keys(obj)){
                res[i] = obj[i];
            }
            return res;
        }

4.2 手写深拷贝

深拷贝需要考虑更多的情况,然后实现浅拷贝的递归即可。

        const deepCopy = (obj) =>{
            if(obj === null) return null;
            if(obj instanceof Date) return new Date(obj);
            if(obj instanceof RegExp) return new RegExp(obj);
            if(typeof obj !== 'object') return obj;

            let res = new obj.constructor();
            for(let i of Object.keys(obj)){
                res[i] = deepCopy(obj[i]);
            }
            return res;
        }

但是如果深拷贝存在循环引用的情况呢?比如最后来一句obj.aaa = obj,那么需要通过WeakMap去进行处理:

WeakMap有以下优点:WeakMap对象是key=>value形式,不会重复记录;WeakMap是弱引用,如果不再使用,空间会直接释放。

        let deepCopy = function(obj , hash = new WeakMap()){
            if(obj === null) return obj;
            if(obj instanceof Date) return new Date(obj);
            if(obj instanceof RegExp) return new RegExp(obj);
            if(typeof obj !== 'object') return obj;
	   
	    // 关键语句 判断属性是否已经存在了
            if(hash.has(obj)) return hash.get(obj);
            let cloneObj = new obj.constructor();
            hash.set(obj , cloneObj);
            
            for(let i of Object.keys(obj)){
                cloneObj[i] = deepCopy(obj[i] , hash);
            }
            return cloneObj;
        }