手写深拷贝

前言

在着手写代码之前我们须先知道什么是深拷贝与前拷贝以前二者的区别是什么。

在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中

深拷贝与前拷贝

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

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

下面是一张形象的图片(网图,侵删)

WeChat0f3e284674e08e07f3a606fd670e1137.png

深、浅拷贝的区别

  • 浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

  • 深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

了解到什么是深拷贝之后我们便可以书写代码了
function deepClone(origin, target) {
  target = target || {};
  // 判断数据类型使用
  var toString = Object.prototype.toString;
  var isArr = "[object Array]";

  // 遍历origin
  for (var k in origin) {
    //确保该属性不是从原型上继承而来
    if (origin.hasOwnProperty(k)) {
      //如果该属性为复杂数据类型且不等于null
      if (typeof origin[k] === "object" && origin[k] !== null) {
        // 那便通过上方自定义的toString方法判断该属性是 array 还是 object
        target[k] = toString(origin[k]) === isArr ? [] : {};
        //递归
        deepClone(origin[k], target[k]);
      } else {
        target[k] = origin[k];
      }
    }
  }
  return target;
}
复制代码
测试:

var p = {
  info: {
    name: "zhang san",
    age: 18,
    list: [1, 2, 3, 4],
  },
};

var child = deepClone(p);
child.info.list = [4, 3, 2, 1];
console.log(child); 
/*
    info:
    age: 18
    list: (4) [4, 3, 2, 1]
    name: "zhang san"
    __proto__: Object
    __proto__: Object
*/
console.log(p);

/*
    info:
    age: 18
    list: (4) [1, 2, 3, 4]
    name: "zhang san"
    __proto__: Object
    __proto__: Object
*/


复制代码

可以看到,拷贝已然成功 ^ ^

但是还有问题如下:


var p = {
  info: {
    name: "zhang san",
    age: 18,
    list: [1, 2, 3, 4],
    regex: /\w/,
    date: new Date(),
  },
};

复制代码

我们新增Date 和Regex数据类型,未能正常拷贝:


    info:
    age: 18
    date: {}
    list: (4) [4, 3, 2, 1]
    name: "zhang san"
    regex: {}
    __proto__: Object
    __proto__: Object

复制代码

所以,这二种属性类型我们要单独领出来处理:


    function deepClone(origin, target) {
      if (origin === undefined || typeof origin !== "object") {
          return origin;
      }
      if (origin instanceof Date) {
        return new Date(origin);
      }
      if (origin instanceof RegExp) {
        return new RegExp(origin);
      }
        ...
    }

复制代码
测试如下:

var p = {
  info: {
    name: "zhang san",
    age: 18,
    list: [1, 2, 3, 4],
    regex: /\w/,
    date: new Date(),
  },
};

var child = deepClone(p);
child.info.list = [4, 3, 2, 1];
console.log(child);  
/*    
    info:
    age: 18
    date: Sun Jun 27 2021 15:36:39 GMT+0800 (中国标准时间) {}
    list: (4) [4, 3, 2, 1]
    name: "zhang san"
    regex: /\w/
    __proto__: Object
    __proto__: Object

*/
console.log(p);

/*
    info:
    age: 18
    date: Sun Jun 27 2021 15:36:39 GMT+0800 (中国标准时间) {}
    list: (4) [1, 2, 3, 4]
    name: "zhang san"
    regex: /\w/
    __proto__: Object
    __proto__: Object
    
*/

复制代码

拷贝已然成功

然鹅。。。


    var test1 = {};
    var test2 = {};
    test1.test2 = test2;
    test2.test1 = test1;
    console.log(deepClone(test2));

复制代码

WeChat79a5cbc35afda125e6298d86aa2c8c1a.png

由log得出是因为拷贝死循环带来的堆栈溢出

然后便引发出了es6的新API WeakMap;

首先介绍下普通object Map WeakMap的异同:

{}:key => string 普通对象的key值是一个普通的string;

Map: key => any 任意数据类型 包括 {} / []

WeakMap: key => object WeakMap的key值必须是一个对象

且:

WeakMap 的键名所引用的对象是弱引用

这句话什么意思呢?

上代码:

    // 一组基础代码
    //正常情况下:
    
    var btn1 = document.getElementsByClassName('btn1');
    var btn2 = document.getElementsByClassName('btn2');
    
    btn1.addEventListener('click',handleBtn1Click,false);
    btn2.addEventListener('click',handleBtn2Click,false);
        
    function handleBtn1Click() {
        ...
    }
    function handleBtn2Click() {
        ...
    }
    
    //如果我们移除了两个节点 但是两个事件处理函数是不会被销毁的
    btn1.remove();
    btn2.remove();
    
    //所以需要我们手动销毁
    handleBtn1Click = null;
    handleBtn2Click = null;

复制代码

所以这个时候就可以用到我们的WeakMap:

    var btn1 = document.getElementsByClassName('btn1');
    var btn2 = document.getElementsByClassName('btn2');
       
    const btnMap = new WeakMap(); // 定义WeakMap   
    
    //弱引用
    btnMap.set(btn1,handleBtn1Click);
    btnMap.set(btn2,handleBtn2Click);
    
    // 这个地方直接 btnMap.key
    btn1.addEventListener('click',btnMap.btn1,false);
    btn2.addEventListener('click',btnMap.btn2,false);
        
    function handleBtn1Click() {
        ...
    }
    function handleBtn2Click() {
        ...
    }
    
     /*
     当我们移除了btn1、btn2两个节点 btnMap里面key为btn1、btn2的两个key值也会自动被销
     毁。key值被销毁,value值也不复存在了。所以,与其说 WeakMap是弱引用,不如说WeakMap的key是弱引用。
    */
    btn1.remove();
    btn2.remove();
复制代码

如此,我们便要修改下代码:

                                // 使用WeakMap
    function deepClone(origin, hash = new WeakMap()) {
        ...
        
       if (origin instanceof RegExp) {
        return new RegExp(origin);
       }
       
       var hashMap = hash.get(origin);
       //判断有没有被重复引用
       if (hashMap) {
        return hashMap;
       }
       
       const target = new origin.constructor();
       
       //每次调用都记录下
       hash.set(origin, target);
       for (const key in origin) {
           ...
       }
       return target;
    }

复制代码

结果如下所示:

WeChat8584fb1e5509c11b7dbf9affdfccd211.png

有的朋友可能发现了这么一行代码:

const target = new origin.constructor();

我来解释下:

    var obj = {};
    var newObj = obj.constructor();
    obj.a = 1;
    console.log(obj);
    console.log(newObj);

    var arr = [];
    arr.push(1);
    var newArr = arr.constructor();
    console.log(arr);
    console.log(newArr);

复制代码

WeChatddb56fbedb0f7f9fa7621440279f4661.png

通过这几行代码,我们可以知道: 我们无需关注origin究竟是{}还是[],只需要根据origin的数据类型origin.construcotr(),重新复制一个{}或者[]。

完整代码如下:

  function deepClone(origin, hash = new WeakMap()) {
      if (origin === undefined || typeof origin !== "object") {
        return origin;
      }
      if (origin instanceof Date) {
        return new Date(origin);
      }
      if (origin instanceof RegExp) {
        return new RegExp(origin);
      }
      
      var hashMap = hash.get(origin);
      
      if (hashMap) {
        return hashMap;
      }
      
      const target = new origin.constructor();
      hash.set(origin, target);
      for (const key in origin) {
          if (origin.hasOwnProperty(key)) {
            target[key] = deepClone(origin[key], hash);
          }
      }
      return target;
}

复制代码

如若发现有何差错,欢迎大家在评论区指正

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享