JavaScript 深浅拷贝

  • 浅拷贝:拷贝的是对象的指针,修改内容相互影响
  • 深拷贝:整个对象拷贝到另一个内存中,修改内容互不影响

浅拷贝

浅拷贝:对原始对象属性值的一份精确拷贝,若属性是基本类型,就拷贝其基本类型的值;若属性是引用类型,则拷贝的是内存地址。所以,若其中一个地址变了后就会影响到另一个对象

实现浅拷贝的方式:

  • Object.assign()
  • ...扩展运算符

深拷贝

深拷贝:对原始对象属性值从内存中完整的拷贝一份,然后在内存中开辟一个新的存储空间存放拷贝对象。所以,若其中一个对象变了不会影响另一个对象

乞丐版

JSON.parse(JSON.stringify())

这种方式最简单,但有以下弊端:

  1. 当被拷贝对象中有Date对象,则拷贝后时间将以字符串的形式
  2. 当被拷贝对象中有RegExpError对象,则拷贝的结果将得到一个空对象
  3. 当被拷贝对象中有undefined和函数的时,则拷贝的结果将会把函数或undefined丢失 通过以上的缺陷,我们可以得出该方法是一个不合格的深拷贝方法

简单版本

注意

  1. 若是原始类型,无需继续拷贝,直接返回
  2. 若是引用类型,则需递归遍历
function clone(target) {
  if (target === 'object') {
    let cloneTarget = {}
    for (let key in target) {
      cloneTarget[key] = clone(target[key])
    }
    return cloneTarget
  } else {
    return target
  }
}

初版主要运用了递归思想,但没有考虑数组的情况

数组情况

function clone(target) {
  if (typeof target === 'object') {
    let cloneTarget = Array.isArray(target) ? [] : {} // 考虑数组
    for (let key in target) {
      cloneTarget[key] = clone(target[key])
    }
    return cloneTarget
  } else {
    return target
  }
}

再升级

考虑循环引用,导致内存溢出

function clone(target, map = new Map()) {
  if (typeof target === 'object') {
    let cloneTarget = Array.isArray(target) ? [] : {}
    if (map.get(target)) {
      // 检查map中有无克隆过的对象, 有则直接返回
      return map.get(target)
    }
    // 没有 - 将当前对象作为key,克隆对象作为value进行存储
    map.set(target, cloneTarget)
    for (let key in target) {
      cloneTarget[key] = clone(target[key], map)
    }
    return cloneTarget
  } else {
    return target
  }
}

再升级

使用 weakMap 代替 Map。因为弱引用类型会自动被垃圾回收掉重点

再升级

判断引用类型,要考虑nullfunction的特殊情况

function isObject(target) {
  const type = typeof target
  return target !== null && (type === 'object' || type === 'function')
}

function clone(target) {
  if (!isObject(target)) {
    // 不是对象
    return target
  } else {
    // 是对象
  }
}

// 获取数据类型
function getType(target) {
  return Object.prototype.toString.call(target)
}