全回顾扫盲之关于JS中那些深拷贝和浅拷贝的知识梳理

js存在深拷贝和浅拷贝的原因,是存储方式

js的存储方式有栈(stack)和堆(heap)。

  • 基本类型数据,大小明确,数据简单,放在栈内存中
  • 引用类型数据,大小不确定,变量保存的是一个指针,数据放在堆内存中

因为这样,所以出现了传值和传址的区别。基本类型,是直接赋值的。但是引用类型,传来传去都是在传递一个放在对内存中的地址

基本类型的拷贝和引用类型的拷贝

对于基本类型,不存在深拷贝和浅拷贝,直接赋值就行。但是对于引用类型,赋值意味着赋予的一个指针,所以后面的变化前面的值也会跟着变。看下面两个栗子

    var name1 = '小明'
    var name2 = name1
    name2 = '小黑'
    console.log('name1', name1)
    console.log('name2', name2)
  

这里,name2的变化并没有影响到name1

    var obj3 = {
      names: '小明',
      age: '21'
    }
    var obj4 = obj3
    obj4.sex = '男'

    console.log('obj3', obj3)
    console.log('obj4', obj4)
  

这里obj4的变化,引起了obj3的同时变化

这时,对于引用类型的拷贝,就不能单纯的赋值,而是要进行深拷贝

下面是我做的一个一层深拷贝的demo。经过两次数据类型的判断,第一次是判断是否为引用类型,因为所有引用类型都出自Object。

但是我这里把引用类型分为了object对象和array对象,没有对Function对象进行处理,姑且忽略

    var obj3 = {
      names: ['小明', '小黑', '大宝', '二丫'],
      age: '21'
    }
    var obj4 = copyDeepOnce(obj3)
    obj4.names.push('闪闪')
    console.log('obj3', obj3)
    console.log('obj4', obj4)

    function copyDeepOnce (originObj) {
      var targetObj = {}
      for (var item in originObj) {
        var val = originObj[item]
        if (val instanceof Object) {
          if (isArray(originObj[item])) {
            targetObj[item] = copyArr(originObj[item])
          } else {
            targetObj[item] = copyObj(originObj[item])
          }
        } else {
          targetObj[item] = originObj[item]
        }
      }
      return targetObj
    }
    function isArray (value) {
      return !!value && value instanceof Array
    }
    function copyArr(arrOrigin) {
    var arr = []
    for (var i = 0; i & lt; arrOrigin.length; i++) {
        arr.push(arrOrigin[i])
    }
    return arr
}

function copyObj(objOrigin) {
    var obj = {}
    for (var item in objOrigin) {
        obj[item] = objOrigin[item]
    }
    return obj
}

这里,obj4的改变并没有影响到obj3,因为对obj4中的值,赋值赋予的是基本类型的值,而不是直接的引用。

多层深拷贝,原理上是利用递归,最终返回到基本类型赋值而不是引用类型的赋值

深拷贝的原理,就是将引用类型的赋址变成基础类型的赋值。这里我用一个递归来实现。

    var obj5 = {
      dataList: [
        {'name': '小明', 'age': 23},
        {'name': '小黑', 'age': 28},
        {'name': '大宝', 'age': 32},
        {'name': '二丫', 'age': 22}
      ],
      admin: {
        'userName': 'mzy',
        'token': 'qeqwe.qweqwewqeqwe.qweqwe'
      }
    }
    var obj6 = copyDeep(obj5)
    obj6.dataList.push({'name': '闪闪', 'age': 19})
    console.log(obj5)
    console.log(obj6)

    function copyDeep(origin) {
      if (origin instanceof Object) {
          if (isArray(origin)) {
              var target = []
              for (var i = 0; i & lt; origin.length; i++) {
                  target.push(copyDeep(origin[i]))
              }
              return target
          } else {
              var target = {}
              for (var item in origin) {
                  target[item] = copyDeep(origin[item])
              }
              return target
          }
      } else {
          return origin
      }
  }

  function isArray(value) {
      return !!value & amp; & amp;
      value instanceof Array
  }

  function copyArr(arrOrigin) {
      var arr = []
      for (var i = 0; i & lt; arrOrigin.length; i++) {
          arr.push(arrOrigin[i])
      }
      return arr
  }

  function copyObj(objOrigin) {
      var obj = {}
      for (var item in objOrigin) {
          obj[item] = objOrigin[item]
      }
      return obj
  }

当然,还有一个更简单的多层深拷贝的方法,就是直接转成字符串,然后再转回来

  var obj1 = {
    token: 'adsf',
    data: {
      name: '小明',
      age: 23
    }
  }
  var obj2 = JSON.parse(JSON.stringify(obj1))
  obj2.data.sex = '男'
  console.log('obj1', obj1)
  console.log('obj2', obj2)
  

但是当值是undefined的时候,属性会被过滤掉

  var obj1 = {
    token: 'adsf',
    data: {
      name: '小明',
      age: undefined
    }
  }
  var obj2 = JSON.parse(JSON.stringify(obj1))
  obj2.data.sex = '男'
  console.log('obj1', obj1)
  console.log('obj2', obj2)
  

然后还有一个ECMA6提供的 Object.assign() 方法,看这里

随机浏览