JavaScript 手写深拷贝

64 阅读2分钟

背景:

因为对象是复杂数据类型,复制的是对象的引用地址,实际上指向的是同一个数据,所以改变了复制的对象的值,源对象的值也会被改变。 但这往往不是我们想要的,所以要进行深拷贝

拷贝方法(深浅):

  • Object.assign(),Array.slice(), Array.concat() 都是浅拷贝,
    • 也就是只有第一层的修改不会影响源对象,深层次的就会影响
  • JSON.parse(JSON.stringify(obj)):JSON.stringify()本意是将对象转为JSON格式,JSON格式序列化本身就不支持fn、undefined、Symbol、循环引用,所以使用 也不够完善
  • 手写递归拷贝 ---->👇🏻

思考过程:

  1. 不是对象和数组的话,直接返回本身 (可以思考下是否需要处理Date 和RegExt的情况)
  2. 为了避免循环引用,要传入一个hash WeakMap();
  3. 声明初始值的时候要区分是数组类型还是对象类型;
  4. 使用for in 遍历对象,但因for in遍历的包括原型链上的属性,所以要使用hasOwnProperty 过滤掉
  5. deepCopy() 的返回值赋值给新对象里的对应key。

forin 也可以遍历数组,for of 不能遍历对象

直接上代码:


function deepCopy(obj, hash= new WeakMap()){
  
    let isOject = Object.prototype.toString.call(obj) == '[object Object]';
    let isArray = Array.isArray(obj);

    if(!isArray && !isOject){
        // 不是对象也不是数组: RegExt Date fn 
        // function 的返回值是[object Function]
        // reg 的返回值是[object RegExt]
        return obj;
    }
   
    if(hash.has(obj)){
        // 循环引用自身
        return hash.get(obj);
    }
   
    let new_obj =  Array.isArray(obj) ?  new Array(): new Object();
    hash.set(obj,'key');

    for(let key in obj){ // in 遍历包括原型链上的属性
       if(obj.hasOwnProperty(key)){ // 判断只是自身的属性,过滤掉原型链上的属性
          new_obj[key] =  deepCopy1(obj[key],hash);
       }      
    }
    return new_obj;

}


let obj1 = {
    name: "初中",
    description: "初中休学一年"
};

var person = {
    name:Symbol('张小'),
    age:undefined,
    reg:new RegExp(/\w/),
    time:new Date(),
    school:{
        level1:"小学",
        level2: obj1,
        level3:{
            name:"高中",
            friends:["小红","小白"],
            getTeacher: function(){return 'zhang'},
        }
    }
}
obj1.name = person; // 互为循环引用

console.log("深拷贝对象::----", deepCopy(person));