js深拷贝(比较完美的实现)

152 阅读1分钟
const deepClasses = ['Map', 'Set', 'Array', 'Object', 'Arguments']

function classof(o) {
  return Object.prototype.toString.call(o).slice(8, -1)
}

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

function getValue(classConstructor, o) {
  return classConstructor.prototype.valueOf.call(o)
}

function getInit(target) {
    const Ctor = target.constructor
    return new Ctor()
}

function cloneFunction(source) {
  if (source.prototype) {
    return function() {
      return source.apply(this, arguments)
    }
  } else {
    return eval(source.toString())
  }
}

function cloneOtherType(source, className) {
  const Ctor = source.constructor
  
  switch (className) {
    case 'Boolean':
    case 'Number':
    case 'String':
    case 'Error':
    case 'Date':
      return new Ctor(getValue(Ctor, source))
    case 'RegExp':
      return new Ctor(source.source, source.flags)
    case 'Symbol':
      return Object(getValue(Ctor, source))
    case 'Function':
      return cloneFunction(source)
    default:
      return new Ctor
  }
}

function cloneDeep(source, allObj = new WeakMap) { //allObj保存原对象的所有对象以及克隆对象中对应的对象
  let target = null

  if (isObject(source)) {
    const className = classof(source)

    if (deepClasses.includes(className)) { //普通对象
      target = getInit(source)
    } else {
      target = cloneOtherType(source, className)
    }

    /* 防止循环引用 */
    if (allObj.has(source)) {
      return allObj.get(source)
    } else {
      allObj.set(source, target)
    }

    if (className === 'Map') {
      source.forEach((value, key) => {
        target.set(key, cloneDeep(value, allObj))
      })
    } else if (className === 'Set') {
      source.forEach(value => {
        target.add(cloneDeep(value, allObj))
      })
    }

    const keys = Reflect.ownKeys(source)
    keys.forEach(key => {
      const value = source[key]
      Object.defineProperty(target, key, {value: cloneDeep(value, allObj)})
    })
  } else {
    target = source
  }

  return target
}