推荐答案
-- -------------------- ---- ------- -------- -------------- ---- - --- ---------- - -- ---- --- ---- -- ------ --- --- --------- - ------ ---- - -- --------------- - ------ -------------- - ----- --------- - ------------------ - -- - --- ------------------------ --- ------ --- -- ---- - -- -------------------------------- ----- - -------------- - ------------------------- - - ------ ---------- -
本题详细解读
深拷贝概念
深拷贝是指创建一个新的对象,这个新对象与原始对象拥有相同的值,但是它们在内存中占据不同的位置。修改深拷贝后的对象不会影响到原始对象,反之亦然。与浅拷贝不同,深拷贝会递归地复制对象的所有层级,包括嵌套的对象和数组。
循环引用问题
当对象中存在循环引用时,即对象属性指向自身或其祖先对象时,传统的深拷贝方法会陷入无限递归,导致栈溢出。解决循环引用问题的关键在于记录已经拷贝过的对象,并在拷贝过程中优先查找是否已经拷贝过。
实现方法详解
基础类型和 null 处理:
- 如果要拷贝的值是
null
或者不是object
类型(例如:string
,number
,boolean
,undefined
,symbol
),直接返回该值。
- 如果要拷贝的值是
循环引用检测:
- 使用
WeakMap
来存储已经拷贝过的对象,WeakMap
的键是原始对象,值是拷贝后的对象。 - 在拷贝开始时,检查
WeakMap
中是否已经存在当前对象,如果存在,则直接返回WeakMap
中存储的拷贝对象,避免无限递归。
- 使用
创建新对象:
- 根据原始对象的类型,创建一个新的空对象或空数组,准备存放拷贝后的属性和值。
存储已拷贝对象:
- 将原始对象作为键,拷贝的新对象作为值存入
WeakMap
中,方便后续的循环引用检测。
- 将原始对象作为键,拷贝的新对象作为值存入
递归拷贝:
- 遍历原始对象的所有属性,使用
Object.hasOwnProperty.call
确保只遍历对象自身的属性,不包含原型链上的属性。 - 对于每个属性值,递归调用
deepClone
函数,将当前属性值和已经构建的WeakMap
作为参数传入。 - 将递归拷贝返回的结果赋值给新对象的对应属性。
- 遍历原始对象的所有属性,使用
返回拷贝对象:
- 完成所有属性拷贝后,返回新创建的拷贝对象。
为什么使用 WeakMap
WeakMap
的键必须是对象,符合我们的拷贝需要。WeakMap
的键是弱引用,不会阻止垃圾回收,当原始对象被回收后,对应的WeakMap
项也会被自动移除,避免内存泄漏。WeakMap
可以高效的查询键是否存在。