资深前端都在用的对象深拷贝

186 阅读3分钟

        /* 
        对象深复制  参数   
           source  源对象  需要复制的对象
           target  目标对象  可以传入已有的对象,也可以由函数返回创建的新对象
        return
           返回目标对象
        对象深复制,可以复制对象中数值、字符、布尔值、undefined、null、object,
        数组,set,map,函数,DOM对象,Date,正则的值,可以复制字符属性名和symbol属性名
        的内容,可以对对象的属性原有值描述类型进行复制(包括不可删除,不可枚举,只读)。
      */
      function cloneObject(source,target={}){
        //   创建一个用来判断复制传入参数的类型列表
           var ClassList=[Set,Map,Date,RegExp];
        //    获取源对象中所有字符串属性名和symbol属性名的数组
           var keys=Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source));
        //   遍历这个属性名数组
           for(var i=0;i<keys.length;i++){
            //    如果是函数,函数因为特殊,包含一个prototype属性,这个属性下会引用当前函数自身,就会造成不断迭代进入死循环,
            // 因此如果是该属性时,跳出当前循环继续下一次
               if(keys[i]==="prototype") continue;
            //    获取当前源对象的所有属性名对应的值得描述对象,使用这个可以对复制后的属性也保留这种设置(包括不可删除,不可枚举,只读)
               var desc=Object.getOwnPropertyDescriptor(source,keys[i]);
            //    如果当前属性名对应的属性值继承与Object,也就是引用类型时,创建新对象并完成递归深复制
            // desc.value 就是对象的这个属性名对应的值(key value中的value)
                if(desc.value instanceof Object){
                    var o;
                    // 新建对象
                    // 如果这个值是DOM元素,判断值是否继承HTMLElement,就是DOM对象
                    if(desc.value instanceof HTMLElement){
                        // DOM克隆
                        o=desc.value.cloneNode(true);
                        // 判断当前值得类型是不是类型列表中的其中一个,包括Set,Map,Date,RegExp
                    }else if(ClassList.includes(desc.value.constructor)){
                        // 如果是这四种,通过new 类型(对象)可以完成对于原对象的复制
                        o=new desc.value.constructor(desc.value);
                        // 如果当前值是函数
                    }else if(desc.value.constructor===Function){
                        //将函数转为数组,并且将换行删除,然后用正则选择函数参数部分和函数{}内所有内容,参数为数组的第0项,函数的内容是数组的第1项
                        var arr=desc.value.toString().replace(/\n/g,"").match(/function\s*\((.*?)\)\s*\{(.*)\}/).slice(1);
                        // 创建函数,并且将参数和内容传入
                        o=new Function(arr[0],arr[1]);
                    }else{ 
                        // 剩余所有类型,都通过new 类型() 的方式来创建这个类型的新对象
                        o=new desc.value.constructor();
                    }
                    // 将这个属性的描述对象说明描述项设置给该属性,并且将值设为刚才创建的新对象
                    Object.defineProperty(target,keys[i],{
                        value:o,
                        configurable:desc.configurable,
                        writable:desc.writable,
                        enumerable:desc.enumerable
                    })
                    // 完成递归,将当前值作为源对象,新创建的对象作为目标对象,递归继续深入复制,因为o对象是引用关系,所以深复制后,这个属性也会改变
                    cloneObject(desc.value,o);
                }else{
                    // 如果这个属性值不是引用类型,则将这个属性值的描述对象和值设置给这个目标对象对应的值
                    // 将value设置给目标对象的key
                    Object.defineProperty(target,keys[i],desc);
                }
           }
           return target;
      }