Javascript 前端私房菜(二)-- 实现一个深拷贝

125 阅读2分钟

实现一个深拷贝,是前端经常遇到的一个问题。不论是在时间中,还是在面试中。。。 下面是两种比较好的实现深拷贝的方式。 实现方案一:


function clone(target, map = new WeakMap()) {
  // 克隆原始类型
  if (!isObject(target)) {
    return target
  }

  // 初始化
  const type = getType(target)
  let cloneTarget
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type)
  } else {
    return cloneOtherType(target, type)
  }

  // 防止循环引用
  if (map.get(target)) {
    return map.get(target)
  }
  map.set(target, cloneTarget)

  // 克隆set
  if (type === setTag) {
    target.forEach((value) => {
      cloneTarget.add(clone(value, map))
    })
    return cloneTarget
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value, map))
    })
    return cloneTarget
  }

  // 克隆对象和数组
  const keys = type === arrayTag ? undefined : Object.keys(target)
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value
    }
    cloneTarget[key] = clone(target[key], map)
  })

  return cloneTarget
}

module.exports = {
  clone,
}

上面的代码,需要学到: 基本实现: 递归能力 循环引用: 考虑问题的全面性 理解weakmap的真正意义 多种类型: 考虑问题的严谨性 创建各种引用类型的方法,JS API的熟练程度 准确的判断数据类型,对数据类型的理解程度 通用遍历: 写代码可以考虑性能优化 了解集中遍历的效率 代码抽象能力 拷贝函数: 箭头函数和普通函数的区别 正则表达式熟练程度 感谢ConardLi的文章:(如何写出一个惊艳面试官的深拷贝)[cloud.tencent.com/developer/a…]

实现方案二:

function deepClone(origin, target, hash = new WeakMap()) {
  // origin:要被拷贝的对象
  // 需要完善,克隆的结果和之前保持相同的所属类
  var target = target || {}

  // 处理特殊情况
  if (origin == null) return origin // null 和 undefined 都不用处理
  if (origin instanceof Date) return new Date(origin)
  if (origin instanceof RegExp) return new RegExp(origin)
  if (typeof origin !== "object") return origin // 普通常量直接返回

  // 防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可
  if (hash.has(origin)) return hash.get(origin)
  hash.set(origin, target) // 制作一个映射表

  // 拿出所有属性,包括可枚举的和不可枚举的,但不能拿到symbol类型
  var props = Object.getOwnPropertyNames(origin)
  props.forEach((prop, index) => {
    if (origin.hasOwnProperty(prop)) {
      if (typeof origin[prop] === "object") {
        if (Object.prototype.toString.call(origin[prop]) == "[object Array]") {
          // 数组
          target[prop] = []
          deepClone(origin[prop], target[prop], hash)
        } else if (
          Object.prototype.toString.call(origin[prop]) == "[object Object]"
        ) {
          // 普通对象
          target[prop] = {}

          deepClone(origin[prop], target[prop], hash)
        } else if (origin[prop] instanceof Date) {
          // 处理日期对象
          target[prop] = new Date(origin[prop])
        } else if (origin[prop] instanceof RegExp) {
          // 处理正则对象
          target[prop] = new RegExp(origin[prop])
        } else {
          // null
          target[prop] = null
        }
      } else if (typeof origin[prop] === "function") {
        var _copyFn = function (fn) {
          var result = new Function("return " + fn)()
          for (var i in fn) {
            deepClone[(fn[i], result[i], hash)]
          }
          return result
        }
        target[prop] = _copyFn(origin[prop])
      } else {
        // 除了object、function,剩下都是直接赋值的原始值
        target[prop] = origin[prop]
      }
    }
  })

  // 单独处理symbol
  var symKeys = Object.getOwnPropertySymbols(origin)
  if (symKeys.length) {
    symKeys.forEach((symKey) => {
      target[symKey] = origin[symKey]
    })
  }
  return target
}


// 测试代码
let s1 = Symbol("s1")
let obj = {
  a: "100",
  b: undefined,
  c: null,
  d: Symbol(2),
  e: /^\d+$/,
  f: new Date(),
  g: true,
  arr: [10, 20, 30],
  school: {
    name: "cherry",
    [s1]: "s1",
  },
  fn: function fn() {
    console.log("fn")
  },
}
obj.h = obj
let obj2 = deepClone(obj)
console.log(obj, obj2)

感谢作者LBJ, 文章参考: (一篇彻底搞定对象的深度克隆 | 包括function和symbol类型)[juejin.cn/post/697441…]