js中如何实现深浅拷贝?

1,468 阅读3分钟

image.png 项目开发中经常会遇到需要复制一个对象或者数组,但是却不能改变原始对象,所以就要用到拷贝,拷贝又分为深拷贝和浅拷贝

浅拷贝

自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。浅拷贝的方法有很多如Object.assign(),扩展运算符方式{...obj},数组 Array.from()等。就不一一列举了,手写一个浅拷贝

思路

  • 对基础类型做一个最基本的拷贝;
  • 对引用类型开辟一个新的存储,并且拷贝一层对象属性;
    var Obj = { 
            func: function () { alert(1) },
            obj: {a:1,b:{c:2}},
            arr: [1,2,3],
            und: undefined, 
            reg: /123/,
            date: new Date(0), 
            NaN: NaN,
            infinity: Infinity,
            sym: Symbol(1)
      }
  const shallowClone = (target) => {
    if (typeof target === 'object' && target !== null) {
      const cloneTarget = Array.isArray(target) ? []: {};
      for (let prop in target) {
        if (target.hasOwnProperty(prop)) {
            cloneTarget[prop] = target[prop];
        }
      }
      return cloneTarget;
    } else {
      return target;
    }
  }
  shallowClone(Obj)

结果如下

image.png

深拷贝

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离

JSON.stringfy()是目前开发过程中最简单的深拷贝方法但是该方法拷贝也会存在一些问题,如下代码

    var Obj = { 
        func: function () { alert(1) },
        obj: {a:1},
        arr: [1,2,3],
        und: undefined, 
        reg: /123/,
        date: new Date(0), 
        NaN: NaN,
        infinity: Infinity,
        sym: Symbol(1)
  }
  Object.defineProperty(Obj,'innumerable',{ 
  enumerable:false,
  value:'innumerable'
  })
  JSON.parse(JSON.stringfy(Obj));

结果如图所示 image.png 注意

  • 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
  • 无法拷贝对象的原型链、不可枚举的属性,以及对象的循环应用,即对象成环 (obj[key] = obj);
  • 拷贝 Date 引用类型会变成字符串;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null; 该方法未拷贝对象所有属性,但是足以满足日常开发去需求

递归版深拷贝

    //需要拷贝的对象
     var obj = {
      num: 0,
      str: '',
      boolean: true,
      unf: undefined,
      nul: null,
      obj: { name: '对象', id: 1, gender: 1  },
      arr: [0, 1, 2],
      func: function () { console.log('函数') },
      date: new Date(0),
      reg: new RegExp('/正则/ig'),
      [Symbol('1')]: 1,
    };
    Object.defineProperty(obj, 'innumerable', {
      enumerable: false, value: '不可枚举属性' }
    );
    obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
    obj.loop = obj    // 设置loop成循环引用的属性
    //判断数据类型
    function ifType(val){
      let type  = typeof val;
      if (type !== "object") {
        return type;
      }
      return Object.prototype.toString.call(val).replace(/^\[object (\S+)\]$/, '$1');
    }
    //拷贝代码
    const deepClone = function (obj, hash = new WeakMap()) {
      if (ifType(obj) === 'Date') 
      return new Date(obj)       // 日期对象直接返回一个新的日期对象
      if (ifType(obj) === 'RegExp')
      return new RegExp(obj)     //正则对象直接返回一个新的正则对象
      //如果循环引用了就用 weakMap 来解决
      if (hash.has(obj)) return hash.get(obj)
      let allDesc = Object.getOwnPropertyDescriptors(obj)
      //遍历传入参数所有键的特性
      let copyObj = Object.create(Object.getPrototypeOf(obj), allDesc)
      //继承原型链
      hash.set(obj, copyObj)
      const isType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
      for (let key of Reflect.ownKeys(obj)) { 
        copyObj[key] = (isType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
      }
      return copyObj
    }
 //验证
let copyObj = deepClone(obj)
copyObj.arr.push(4)
console.log('原始对象obj', obj)
console.log('拷贝后的对象copyeObj', copyObj)

image.png

除上以外,函数库lodash提供的_.cloneDeep方法也可以实现深拷贝