深拷贝&浅拷贝

对于基本数据类型,没有深浅拷贝的区别。深浅拷贝只针对引用数据类型。 如果我们要赋值对象的所有属性都是引用类型可以用浅拷贝

# 概念

  • 浅拷贝:只复制一层对象,当对象的属性都是引用类型时,实质复制的是其引用,当引用值发生改变时,也会跟着改变
  • 深拷贝:另外申请一块内存,内容跟原对象一样,更改原对象,不会改变拷贝对象

# 原因

  • 基本数据类型存在栈stack, 栈内存上的分配的空间生命周期很短,方法执行完毕就被释放掉
  • 引用数据类型存在堆内存heap, js不允许直接访问堆内存中的位置,不能直接操作对象的堆内存空间
    • 读取对象时,实际上是在操作对象的引用地址而不是实际的对象
    • 引用地址保存在栈内存中

# 实现

  • 浅拷贝

    • for-in遍历

      let copyObj = function(obj) {
          let rst = {}
          for (let key in obj) {
              if (obj.hasOwnProperty(key)) {
                  rst[key] = obj[key]
              }
          }
          return rst
      }
      
      let obj1 = {
          name: '迪丽热巴',
          sex: 'female',
          friend: {
              name: 'Ethan'
          }
      }
      let obj2 = copyObj(obj1)
      obj2.name = '古力娜扎'
      obj2.friend.name = 'FU'
      console.log(obj1.friend.name) // FU
      
    • Object.assign(target, source)

      let obj1 = {
          name: '迪丽热巴',
          sex: 'female',
          friend: {
              name: 'Ethan'
          }
      }
      let obj2 = Object.assign({}, obj1)
      obj2.name = '古力娜扎'
      obj2.friend.name = 'FU'
      console.log(obj1.friend.name) // FU
      
    • ...拓展运用符

      let obj1 = {
          name: '迪丽热巴',
          sex: 'female',
          friend: {
              name: 'Ethan'
          }
      }
      let obj2 = {...obj1}
      obj2.name = '古力娜扎'
      obj2.friend.name = 'FU'
      console.log(obj1.friend.name) // FU
      
    • slice(适用于数组)

      let a = [1, 2, 3, {name: 'Ethan'}]
      let b = a.slice()
      b[0] = 4
      b[3].name = 'Linna'
      console.log(a, b)
      // a [1, 2, 3, {name: 'Linna'}]
      // b [4, 2, 3, {name: 'Linna'}]
      
  • 深拷贝

    • JSON.parse(JSON.stringify(obj))

      let obj1 = {
          name: 'Ethan',
          car: ['BYD', 'BMW']
      }
      let obj2 = JSON.parse(JSON.stringify(obj1))
      obj1.name = 'Linna'
      obj2.car[0] = 'BENZ'
      console.log(obj1, obj2)
      
      // obj1 {name: 'Ethan',car: ['BYD', 'BMW']} obj1.car没有改变,说明实现了深拷贝
      // obj2 {name: 'Linna',car: ['BENZ', 'BMW']}
      //
      let obj1 = {
          name: 'Ethan',
          car: ['BYD', 'BMW'],
          hit: function() {},
          age: undefined
      }
      let obj2 = JSON.parse(JSON.stringify(obj1))
      console.log(obj2)
      /*
          1. 当对象的属性值为函数或undefined,序列化结果会丢失函数或undefined
          2. 对象中存在循环引用到情况无法实现深拷贝
          3. Symbol不能被JSON序列化
          4. RegExp/Error对象,序列化结果为空对象
          5. 会丢失原型对象
      */
      
    • 递归实现深拷贝

      let deepClone = function(obj) {
          let rst = Array.isArray(obj) ? [] : {}
          if (obj && typeof obj === 'object') {
              for (let key in obj) {
                  if (obj.hasOwnProperty(key)) {
                      if (obj[key]&& typeof obj[key] === 'object') {
                          rst[key] = deepClone(rst[key])
                      } else {
                          rst[key] = obj[key]
                      }
                  }
              }
          }
          return rst
      }
      
      let obj1 = {
          name: 'Ethan',
          car: ['BYD', 'BMW'],
          hit: function() {},
          age: undefined
      }
      let obj2 = deepClone(obj1)
      obj2.name = 'Linna'
      obj2.car = ['BENZ']
      console.log(obj1, obj2)
      // obj1 { age: undefined, car: ['BYD', 'BMW'], hit: ƒ (), name: "Ethan" }
      // obj2 { age: undefined, car: ['BENZ'], hit: ƒ (), name: "Linna" }
      
    • 第三方库

# 注意事项

  • 是否存在循环引用?
  • 传入的对象是使用哪种方式创建?
    • 对象字面量创建
    • 构造函数生成
      • 是否要拷贝原型链上的属性
      • 拷贝的话存在多个同名属性要保留哪个