如何实现对象的深拷贝?请考虑循环引用的情况。

推荐答案

-- -------------------- ---- -------
-------- -------------- ---- - --- ---------- -
  -- ---- --- ---- -- ------ --- --- --------- -
    ------ ----
  -

  -- --------------- -
      ------ --------------
  -


  ----- --------- - ------------------ - -- - ---
  ------------------------
  
  --- ------ --- -- ---- -
    -- -------------------------------- ----- -
      -------------- - -------------------------
    -
  -

  ------ ----------
-

本题详细解读

深拷贝概念

深拷贝是指创建一个新的对象,这个新对象与原始对象拥有相同的值,但是它们在内存中占据不同的位置。修改深拷贝后的对象不会影响到原始对象,反之亦然。与浅拷贝不同,深拷贝会递归地复制对象的所有层级,包括嵌套的对象和数组。

循环引用问题

当对象中存在循环引用时,即对象属性指向自身或其祖先对象时,传统的深拷贝方法会陷入无限递归,导致栈溢出。解决循环引用问题的关键在于记录已经拷贝过的对象,并在拷贝过程中优先查找是否已经拷贝过。

实现方法详解

  1. 基础类型和 null 处理

    • 如果要拷贝的值是 null 或者不是 object 类型(例如:string, number, boolean, undefined, symbol),直接返回该值。
  2. 循环引用检测

    • 使用 WeakMap 来存储已经拷贝过的对象,WeakMap 的键是原始对象,值是拷贝后的对象。
    • 在拷贝开始时,检查 WeakMap 中是否已经存在当前对象,如果存在,则直接返回 WeakMap 中存储的拷贝对象,避免无限递归。
  3. 创建新对象

    • 根据原始对象的类型,创建一个新的空对象或空数组,准备存放拷贝后的属性和值。
  4. 存储已拷贝对象

    • 将原始对象作为键,拷贝的新对象作为值存入WeakMap中,方便后续的循环引用检测。
  5. 递归拷贝

    • 遍历原始对象的所有属性,使用 Object.hasOwnProperty.call 确保只遍历对象自身的属性,不包含原型链上的属性。
    • 对于每个属性值,递归调用 deepClone 函数,将当前属性值和已经构建的 WeakMap 作为参数传入。
    • 将递归拷贝返回的结果赋值给新对象的对应属性。
  6. 返回拷贝对象

    • 完成所有属性拷贝后,返回新创建的拷贝对象。

为什么使用 WeakMap

  • WeakMap 的键必须是对象,符合我们的拷贝需要。
  • WeakMap 的键是弱引用,不会阻止垃圾回收,当原始对象被回收后,对应的 WeakMap 项也会被自动移除,避免内存泄漏。
  • WeakMap 可以高效的查询键是否存在。
纠错
反馈