深浅拷贝

202 阅读4分钟

浅拷贝

创建一个新对象,它会对原对象的属性进行一次拷贝,如果属性的值属于基本数据类型,那就直接拷贝这个值,如果属性的值,是引用类型,那就拷贝引用类型的指针(内存地址),所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝

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

实现浅拷贝的一些方式

1. 可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝

let arr = [1,2,3];
let newArr = arr.concat();
newArr[0] = 4;
console.log(arr)    // [1,2,3]
console.log(newArr) // [4,2,3]

2.Object.assign()

** 注意 Object.assign()第一个参数必须是个空对象

let obj = {a:1,b:2}
let obj2 = Object.assign({}, obj)
obj2.a = 4;
console.log(obj, obj2) // {a:1,b:2}  {a:4,b:2}  

3. 解构赋值

let obj = {a:1,b:2}
let obj2 = {...obj}
obj2.a = 4;
console.log(obj, obj2) // {a:1,b:2}  {a:4,b:2}  

自己实现一个浅拷贝

function lowDeepCopy (obj) {
    if(typeof(obj) === 'object'){
        let newObj = obj instanceof Array ? [] : {};
        for(let key in obj) {
            newObj[key] = obj[key]
        }
        return newObj
    } else {
        return obj;
    }
}

实现深拷贝的一些方式

1. JSON.parse(JSON.stringify())

这个方法是我们日常开发过程中比较常用的方法,但是它也有它的一些弊端,我们通过下面的例子来简述一下JSON.parse(JSON.stringify())的弊端

let obj = {
    a: 2,
    fun: function () {
        console.log(22);
    },
    symbol: Symbol('aaa'),
    d: undefined,
    reg: new RegExp('\\w+'),
    err: new Error('ss'),
    date: new Date(1536627600000)  // Tue Sep 11 2018 09:00:00 GMT+0800
    (中国标准时间)
};
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) 
// 输出结果为
{
    a:2,
    date:"2018-09-11T01:00:00.000Z"
    err:{},
    nan:null
    reg:{}
}

从上面的例子我们发现

  1. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象
  2. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失
   let obj = {
       fun: function () {
           console.log(22);
       }
   };
   let newArr = JSON.stringify(obj, function (key, value) {
       if (typeof value === 'function') {
           return value.toString();
       } else {
           return value;
       }
   });
   console.log(newArr); // {"fun":"function fun() {\n                console.log(22);\n            }"}
  1. 如果obj里有NaN,则序列化的结果会变成null
  2. 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式

时间对象格式问题,可以使用JSON.parse的第二个参数传入每个键值对的key和value进行处理

let obj = {
    date: new Date(1536627600000)
};
let newArr = JSON.stringify(obj);
console.log(newArr); // {"date":"2018-09-11T01:00:00.000Z"}
let resetObj = JSON.parse(newArr, function (key, value) {
    if (key === 'date') {
        return new Date(value);
    } else {
        return value;
    }
});
console.log(resetObj); // {date: Tue Sep 11 2018 09:00:00 GMT+0800 (中国标准时间)}

下面我们尝试着自己实现一个深拷贝

    function deepCopy (obj) {
        if (typeof obj === 'object') {
            let newObj = obj instanceof Array ? [] : {};
            for (let key in obj) {
                newObj[key] = typeof (obj[key]) === 'object' ? deepCopy(obj[key]) : obj[key];
            }
            return newObj;
        } else {
            return obj;
        }
    }

循环引用

    let arr = {
        a: {
            name: 'sj',
            age: 28
        },
        b: 333
    };
    arr.c = arr;
    let newArr = deepCopy(arr);
    console.log(newArr, arr);

运行如上代码,会有爆栈的情况

为了解决这个问题,我们可以引入一个存储空间,把拷贝后的对象存进去,在方法中判断,如果拷贝过这个对象了,那就直接返回,不再继续执行。

我们考虑把拷贝前的对象作为key,把拷贝后的对象作为value,以一种键值对的方式,进行存储。

最常用的存储方式就是WeakMap和Map 相比较而言,WeakMap更合适

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的

Map 对象间是存在强引用关系

弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,当下一次垃圾回收机制执行时,这块内存就会被释放掉。

优化一下我们之前的深拷贝代码

function clone(obj, map = new WeakMap()) {
    if (typeof obj === 'object') {
        let newObj = obj instanceof Array ? ? [] : {};
        if (map.get(obj)) {
            return map.get(obj);
        }
        map.set(obj, newObj);
        for (let key in obj) {
            newObj[key] = typeof (obj[key]) === 'object' ? deepCopy(obj[key], map) : obj[key];
        }
        return newObj;
    } else {
        return obj;
    }
}