「前端每日一问(17)」手写一个深拷贝

437 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本题难度:⭐ ⭐ ⭐

要求:

  • 支持对象、数组、日期、正则的拷贝。
  • 处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
  • 处理 Symbol 作为键名的情况。
  • 处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,没有任何问题)。
  • 处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
  • 额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)。

答:

function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
  if (target === null) return target // 如果是 null 就不进行拷贝操作
  if (target instanceof Date) return new Date(target) // 处理日期
  if (target instanceof RegExp) return new RegExp(target) // 处理正则
  if (target instanceof HTMLElement) return target // 处理 DOM元素

  if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回

  // 是引用类型的话就要进行深拷贝
  if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
  const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
  hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里

  Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
    cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
  })
  return cloneTarget // 返回克隆的对象
}

测试一下:

const obj = {
  a: true,
  b: 100,
  c: 'str',
  d: undefined,
  e: null,
  f: Symbol('f'),
  g: {
    g1: {} // 深层对象
  },
  h: [], // 数组
  i: new Date(), // Date
  j: /abc/, // 正则
  k: function () {}, // 函数
  l: [document.getElementById('foo')] // 引入 WeakMap 的意义,处理可能被清除的 DOM 元素
}

obj.obj = obj // 循环引用

const name = Symbol('name')
obj[name] = 'lin'  // Symbol 作为键

const newObj = deepClone(obj)

console.log(newObj)

image.png

手写深拷贝的过程可参考这篇文章:

轻松拿下 JS 浅拷贝、深拷贝

结尾

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(16)」深拷贝和浅拷贝的区别?

下一篇:

「前端每日一问(18)」JS 中伪数组怎么转成数组