简单分析JavaScript深拷贝

108 阅读2分钟

开发的过程中经常会遇到需要拷贝对象的时候,因为对象是引用型数据类型,在赋值的过程记录的是对象的地址,所以如果多个变量指向的是同一个内存地址,改变一个变量的属性,所有的变量都会收到影响,在对象的拷贝过程中,需要用到深拷贝。

1 定一个变量

var copy; 定义一个变量不进行赋值,因为copy的类型不一定是对象,也可能是数组或者其它的。

2 考虑基本数据类型

if (null == values || "object" != typeof values) return values; 如果类型是null,undefined,string, boolean,number直接返回原值就行。

3 考虑是Date

Date是用来创建时间对象的,实质上也是对象。所以拷贝的过程中也需要摆脱原对象引用地址。这里就需要重新创建一个date实例,然后跟原对象值保持一致。

if (values instanceof Date) {
        copy = new Date();
        copy.setTime(values.getTime());
        return copy;
    }

4 考虑数组

一个对象的属性也可能是数组,数组也是对象,如果直接复制依然会保留对原数组的引用。所以这里也需要新创建一个数组。由于数组中的元素也可能是任何数据类型,这里可以对它使用递归:

if (values instanceof Array) {
        copy = [];
        for (var i = 0, len = values.length; i < len; i++) {
            copy[i] = deepClone(values[i]);
        }
        return copy;
    }

5 考虑对象 如果值还是对象,需要遍历每一个key,这里用的for in遍历。对象的value也可能是任何数据型,所以这里用上了递归。遍历的过程中看到这样一段代码:values.hasOwnProperty(attr)。这么做是出于性能的考虑,for in循环不仅会遍历对象本身的属性,还会遍历原型上可枚举的属性,加上这个判断就避开了原型上的属性。

if (values instanceof Object) {
        copy = {};
        for (var attr in values) {
            if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);
        }
        return copy;
    }

考虑循环引用,下面的写法出现了循环引用:如果直接使用上面的方法拷贝,会报错,超出了最大执行栈。

var obj = {};
obj.a = new Date('2020-01-27');
obj.b = obj;

可以使用数据缓存解决循环引用问题。var map = new WeakMap()的可以只接受对象,value可以是任意值。当遇到下面代码时,就说明存在循环引用对象,代码无需向下执行,直接返回存储好的数据就行。

if(map.has(value)) {
  return map.get(value);
}

总结起来就是:

function wrapDeepClone(value) {
    var  map = new WeakMap()
 
    function deepClone(value) {
   
        var copy, existObj; // key只能是对象类型
  
        if(!value || typeof value !== 'object') return value;
  
        if(value instanceof Date) {
            copy = new Date();
            copy.setTime(value.getTime());
            return copy;
  
        }
  
        if(map.has(value)) {
  
            return map.get(value);
        }
   
        if(value instanceof Array) {
            copy = [];
            map.set(value, copy);
            for(var i = 0; i < value.length; i++) {
                copy[i] = deepClone(value[i]);
            }
            return copy;
        }
  
        if(value instanceof Object) {
            copy = {};
            map.set(value, copy);
            for(var key in value) {
                if(value.hasOwnProperty(key)) {
                    copy[key] = deepClone(value[key])
                }
            }
  
            return copy;
        }
  
    }
    return deepClone(value)
}
var copyObj = wrapDeepClone(obj);
 
console.log(copyObj);