实现深拷贝和浅拷贝

参考资料

  1. js实现浅拷贝与深拷贝的区别于实现方式

前言

js中的数据类型就只有string、number、boolean、object、null、undefined这6种。而浅拷贝和深拷贝只是针对object而言的,其他5种不存在浅拷贝和深拷贝的问题。

一个object其实可以看做是一颗树,树的叶子节点的数据类型是string、number、boolean、null、undefined这5种,而中间节点的数据类型就是object。所谓拷贝,就是生成这样一棵树的副本。深拷贝就是在拷贝的时候遍历到所有的叶子节点,而浅拷贝则遍历到某一层的中间节点就结束了。另外,深拷贝在复制中间节点(即object对象)的时候,不是直接把对方的中间节点拿过来用,而是自己创建一个新对象,否则就是浅拷贝了。

对象的浅拷贝和深拷贝

测试代码

/**
 * 测试copy是否是浅拷贝函数
 */
function testObjectShallowCopy(copy) {
  const desObj = copy(resObj)
  return !isObjectDeepCopy(resObj, desObj)
}

/**
 * 测试copy是否是深拷贝函数
 */
function testObjectDeepCopy(copy) {
  const desObj = copy(resObj)
  return isObjectDeepCopy(resObj, desObj)
}

// 被拷贝的对象
const resObj = {
  l1: 'l1',
  l2: {
    l21: 'l21',
    l22: 'l22',
  }
}

/**
 * 判断是否是深拷贝
 * @param {*} obj1 
 * @param {*} obj2 
 */
function isObjectDeepCopy(obj1, obj2) {
  // 参数检查
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
    throw 'obj1和obj2都必须是对象'
  }
  // 如果两者都是null,则直接返回true
  if (obj1 === null && obj2 === null) {
    return true
  }
  // 如果其中一个是null,另一个不是null,代表拷贝出问题,返回false
  if (!(obj1 === null) === (obj2 === null)) {
    return false
  }
  // 如果两者相等,代表是浅拷贝,返回false
  if (obj1 === obj2) {
    return false
  }
  // 遍历比较两者的第一层属性
  for (let key in obj1) {
    // 如果两者都是对象,且isObjectDeepCopy()返回false,则返回false
    if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object' && isObjectDeepCopy(obj1[key], obj2[key]) === false) {
      return false
    }
    // 如果两者不是对象,且不相等,代表拷贝出问题,返回false
    if (obj1[key] !== obj2[key]) {
      return false
    }
  }
  // 考虑到obj2的属性可能比obj1的属性多,因此最后还要比一下序列化值
  if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
    return false
  }
  return true
}

实现浅拷贝

1)直接赋值

testObjectShallowCopy(function(resObj) {
  const desObj = resObj
  return desObj
},) // true

2)手动遍历

testObjectShallowCopy(function(resObj) {
  const desObj = {}
  for (let key in resObj) { // 只遍历第一层
    desObj[key] = resObj[key]
  }
  return desObj
},) // true

3)Object.assign()

testObjectShallowCopy(function(resObj) {
  const desObj = Object.assign(resObj)
  return desObj
},) // true

实现深拷贝

1)递归遍历

function deepCopy(resObj) {
    const desObj = {}
    for (let key in resObj) {
        if (typeof resObj[key] === 'object' && resObj[key] !== null) {
            desObj[key] = deepCopy(resObj[key])
        } else {
            desObj[key] = resObj[key]
        }
    }
    return desObj
}

testObjectDeepCopy(deepCopy) // true

2)用JSON

testObjectDeepCopy(function(resObj) {
  const desObj = JSON.parse(JSON.stringify(resObj))
  return desObj
}) // true

3)使用Object.create()

testObjectDeepCopy(function(resObj) {
  const desObj = Object.create(resObj)
  return desObj
}) // true

数组的浅拷贝和深拷贝

测试代码

/**
 * 测试copy是否是浅拷贝函数
 */
function testArrayShallowCopy(copy) {
  const desArr = copy(resArr)
  return !isArrayDeepCopy(resArr, desArr)
}

/**
 * 测试copy是否是深拷贝函数
 */
function testArrayDeepCopy(copy) {
  const desArr = copy(resArr)
  console.log(isArrayDeepCopy(resArr, desArr));
  console.log(resArr);
  console.log(desArr);


  return isArrayDeepCopy(resArr, desArr)
}

// 被拷贝的数组
const resArr = [1, [21, [221, [2221]]]]

/**
 * 判断是否是深拷贝
 * @param {*} arr1 
 * @param {*} arr2 
 */
function isArrayDeepCopy(arr1, arr2) {
  // 参数检查
  if (!(arr1 instanceof Array && arr2 instanceof Array)) {
    throw 'arr1和arr2都必须是数组'
  }
  // 如果两个数组相等,代表是浅拷贝,返回false
  if (arr1 === arr2) {
    return false
  }
  // 遍历比较两者的第一层属性
  for (let index in arr1) {
    // 如果两者都是数组
    if (arr1[index] instanceof Array && arr2[index] instanceof Array) {
      // 如果isArrayDeepCopy返回false,则返回false
      if (isArrayDeepCopy(arr1[index], arr2[index]) === false) {
        return false
      }
    }
    // 如果两者都是对象,且isObjectDeepCopy返回false,则返回false
    else if (typeof arr1[index] === 'object' && typeof arr2[index] === 'object' && isObjectDeepCopy(arr1[index], arr2[index]) === false) {
      return false
    }
    // 如果两者不相等,代表拷贝出问题,返回false
    else if (arr1[index] !== arr2[index]) {
      return false
    }
    // 如果两者相等,则放行
  }
  // 考虑到arr2的元素数量可能比arr1多,因此最后还要比一下序列化值
  if (JSON.stringify(arr1) !== JSON.stringify(arr2)) {
    return false
  }
  return true
}

实现浅拷贝

和对象的浅拷贝类似,不赘述。

另外要注意的是,concat()对那些元素中不存在对象或数组的数组来说,也可以实现深拷贝。

实现深拷贝

1)用slice()

testArrayDeepCopy(function(resArr) {
  const desArr = resArr.slice(0)
  return desArr
}) // true

2)用JSON

同对象使用JSON进行深拷贝。

最后

如参考资料1中作者表达的意思,一些浅拷贝方法对于没有嵌套结构(即只有一层,该层的所有属性或元素都是基本元素类型)的对象和数组来说,也可以是深拷贝。因此深拷贝的实现方式并没那么绝对。但如果要写出一个普适性的深拷贝函数,那么得考虑到对象和数组有嵌套结构。

posted @ 2021-09-12 17:57  hdxg  阅读(46)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css