美文网首页
如何实现一个深浅拷贝

如何实现一个深浅拷贝

作者: PYFang | 来源:发表于2021-06-10 09:59 被阅读0次

    浅拷贝&深拷贝

    浅拷贝:只是创建了一个新的对象,复制了原有对象的基本类型的值 ,而引用数据类型只拷贝了一层属性,再深层的还是无法进行拷贝

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

    浅拷贝

    浅拷贝的原理和实现
    自己创建一个新的对象,来接受你要重新复制或者引用的对象值,如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如何属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

    方法1:object.assign
    object.assignES6object的一个方法,该方法可以用于JS对象的合并等多个用途,其中一个用途就是可以进行浅拷贝,该方法的第一个参数是拷贝的目标对象,后面的一个参数是拷贝的来源对象,也可以是多个来源
    语法:Object.assign(target,...sources)

    let target = {}
    let source = {a:{b:1}}
    Object.assign(target,source)
    console.log(target)   // {a:{b:10}}
    source.a.b = 10
    console.log(source)   // {a:{b:10}}
    console.log(target)   // {a:{b:10}}
    

    注:

    • 不会拷贝对象的继承属性
    • 不会拷贝对象的不可枚举的属性
    • 可以拷贝Symbol类型的属性

    可以简单理解为Ojbect.assign循环遍历原对象的属性,通过复制的方式将其赋给目标对象的相应属性。
    方法2:扩展运算符方式
    利用JS的扩展运算符,在构造对象的同时完成浅拷贝的功能
    语法:let cloneObj = {...obj}

    // 对象的拷贝
    let obj = {a:1,b:{c:1}}
    let obj2 = {...obj}
    obj.a = 2
    console.log(obj) // {a:2,b:{c:1}}
    console.log(obj2) // {a:1,b:{c:1}}
    obj.b.c = 2
    console.log(obj) // {a:2,b:{c:2}}
    console.log(obj2) // {a:1,b:{c:2}}
    // 数组的拷贝
    let arr = [1,2,3]
    let newArr = [...arr] // 跟arr.slice()是一样的效果
    

    方法3:concat拷贝数组
    数组的concat方法其实也是浅拷贝

    let arr = [1,2,3]
    let newArr = arr.concat()
    newArr[1] = 100
    console.log(arr) // [1,2,3]
    console.log(newArr) // [1,100,3]
    

    方法4:slice拷贝数组
    slice 方法仅仅针对数组类型,由前两个参数决定数组的开始与结束位置,并不改变原数组
    语法:arr.slice(start,end)

    let arr = [1,2,{val:4}]
    let newArr = arr.slice()
    newArr[2].val = 100
    console.log(arr) // [1,2,{val:100}]
    

    手写一个浅拷贝

     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
       }
     }
    

    深拷贝

    对于复杂引用数据类型,其在堆内存中完全开辟了一块内存地址,并将原有的对象完全复制过来存放

    方法1:JSON.stringfy()
    JSON.stringify()是目前开发过程中最简单的深拷贝方法
    把一个对象序列化成json的字符串并把对象里的内容转换为字符串,最后再用JSONparsejson字符串生成一个新的对象

    let obj1={a:1,b:[1,2,3]}
    let str = JSON.stringify(obj1)
    let obj2 = JSON.parse(str)
    console.log(obj2) // {a:1,b:[1,2,3]}
    obj1.a = 2
    obj1.b.push(4)
    console.log(obj1) // {a:2,b:[1,2,3,4]}
    console.log(obj2) // {a:1,b:[1,2,3]}
    

    注:
    1,拷贝的对象的值中如果有函数、undefinedsymbol这几种类型,经过JSON.stringify序列化之后的字符串中这个键值对会消失
    2,拷贝Date引用类型会变成字符串
    3,无法拷贝不可枚举的属性
    4,无法拷贝对象的原型链
    5,拷贝RegExp(正则)引用类型会变成空对象
    6,对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的结果会变成null
    7,无法拷贝对象的循环应用,即对象成环(obj[key] = obj)

    方法2:手写递归实现(基础版)

    let obj1 = {
      a: {
        b: 1
      }
    }
    function deepClone(obj) {
      let cloneObj = {}
      for (let key in obj) {
        if (typeof obj[key] === 'object') {
          cloneObj[key] = deepClone(obj[key]) // 是对象就再次调用该函数递归
        } else {
          cloneObj[key] = obj[key]  // 基本类型的话直接复制值
        }
      }
      return cloneObj
    }
    let obj2 = deepClone(obj1)
    obj1.a.b = 2
    console.log(obj2) // {a:{b:1}}
    

    上面代码需注意:

    • 这个深拷贝函数并不能复制不可枚举属性以及Symbol类型
    • 这种方法只是针对普通的引用类型的值做递归复制
    • 对象的属性里面成环,即循环引用没有解决

    方法三:递归实现(改进版)
    1,针对能够遍历对象的不可枚举属性以及Symbol类型我们可以使用Reflect.ownKeys方法
    2,当参数为Date、RegExp类型,则直接生成一个新的实例返回
    3,利用ObjectgetOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合Objectcreate方法创建一个新对象,并继承传入原对象的原型链
    4,利用WeakMap 类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄露,作为检测循环引用很有帮助,如果存在循环,则引用直接返回WeakMap存储的值

    const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function' && (obj !== null))
    const deepClone = function(obj, hash = new WeakMap()) {
      if (obj.constructor === Date)
        return new Date(obj) // 日期对象直接返回一个新的日期对象
      if (obj.constructor === RegExp)
        return new RegExp(obj) // 正则对象直接返回一个新的正则对象
      if (hash.has(obj)) return hash.get(obj)
      let allDesc = Object.getOwnPropertyDescriptors(obj)
      // 遍历传入参数所有键的特性
      let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
      //继承原型链
      hash.set(obj, cloneObj)
      for (let key of Reflect.ownKeys(obj)) {
        cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
      }
      return cloneObj
    }
    let obj = {
      num: 0,
      str: '',
      boolean: true,
      obj: { name: '我是一个对象', id: 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成循环引用的属性
    let cloneObj = deepClone(obj)
    cloneObj.arr.push(4)
    console.log('obj', obj)
    console.log('cloneObj', cloneObj)
    

    相关文章

      网友评论

          本文标题:如何实现一个深浅拷贝

          本文链接:https://www.haomeiwen.com/subject/nkoseltx.html