【JavaScript】深拷贝

113 阅读2分钟

思路

深拷贝对象其实就是创建一个新的对象,再将旧对象上的属性或方法添加到新对象上。

当然,要考虑属性是基本类型还是引用类型。如果是基本类型,直接使用即就行;如果是引用类型,则新建一个,然后添加到新对象上;如果是方法,由于方法本质上是函数(但不完全一样, 见 Method definitions are not constructable),函数本来就是用来复用的,所以直接添加到新对象上。

代码

/**
 * 对常用数据类型进行深拷贝,考虑到的情况有:
 * 1. key 为`Symbol`;
 * 2. 原始值为 Array/Function/Date/Set/Map/Object等其他引用类型 的情况;
 * 3. 对象循环引用.
 *
 * @param {*} originValue
 * @returns {*}
 */

function deepClone(originValue) {
  // 记录在递归 originValue 的属性时, 遇到的所有引用类型属性
  const refRecords = new WeakSet()

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

  // 给不同引用类型创建新值
  function createNewValue(_originValue) {
    const cName = _originValue.constructor.name
    let _newValue

    if (cName === 'Array') {
      _newValue = []
      for (const key in _originValue) {
        _newValue[key] = returnNewValue(_originValue[key])
      }
    } else if (cName === 'Date') {
      _newValue = new Date(_originValue)
    } else if (cName === 'Function') {
      _newValue = _originValue // 函数本来就是复用的, 因此没必要再重新拷贝一个
    } else if (cName === 'RegExp') {
      _newValue = new RegExp(_originValue)
    } else if (cName === 'Set') {
      _newValue = new Set()
      // Set 本身可迭代
      _originValue.forEach((value1, value2) => {
        _newValue.add(returnNewValue(value1))
      })
    } else if (cName === 'Map') {
      _newValue = new Map()
      // Map 本身可迭代
      _originValue.forEach((_originValue, key) => {
        _newValue.set(returnNewValue(key), returnNewValue(_originValue))
      })
    } else {
      _newValue = {}

      // 遍历 _originValue 自身的属性
      for (const key of Object.getOwnPropertyNames(_originValue)) {
        _newValue[key] = returnNewValue(_originValue[key])
        // _newValue[key] = _originValue[key] // 循环引用时直接返回原对象, 避免循环引用
      }

      // 遍历 _originValue 的 Symbol 属性
      for (const symbolKey of Object.getOwnPropertySymbols(_originValue)) {
        _newValue[symbolKey] = returnNewValue(_originValue[symbolKey])
      }
    }
    return _newValue
  }

  // 实现深拷贝
  function _deepClone(_originValue) {
    // 非引用类型数据直接返回
    if (!isObject(_originValue)) return _originValue
    // 记录引用类型
    refRecords.add(_originValue)

    return createNewValue(_originValue)
  }

  // 处理循环引用问题
  function returnNewValue(_ref) {
    // 判断引用记录里面是否已存在 _ref 对象, 若存在,说明 _ref 对象被引用过, 直接用 _ref 赋值
    return refRecords.has(_ref) ? _ref : _deepClone(_ref)
  }

  const clonedValue = _deepClone(originValue)

  // refRecords.clear()

  return clonedValue
}