深拷贝遇循环引用会栈溢出,根本原因是递归无限进入同一对象;须用WeakMap缓存已拷贝对象,在重复访问前拦截返回副本以切断递归链,因其支持对象键、不阻止GC、无遍历接口,比Map更安全轻量。

深拷贝遇到循环引用会直接栈溢出,根本原因是递归无限进入同一个对象。用 WeakMap 缓存已拷贝的对象,就能在重复访问前拦截并返回已有副本,彻底切断递归链。
为什么 WeakMap 是最佳选择
WeakMap 允许以对象为键,且不阻止垃圾回收——即使源对象后续被释放,缓存条目也会自动清理,避免内存泄漏。它不暴露遍历接口,天然适合作为内部状态管理工具,比 Map 更轻量、更安全。
关键操作顺序不能错
- 每次进入深拷贝函数时,先查
hash.has(obj);命中就立刻返回hash.get(obj) - 没命中时,立即创建新对象(
new obj.constructor()),并调用hash.set(obj, cloneObj) - 必须 set 在递归调用之前,否则同一对象在不同路径下仍会被多次处理
要覆盖的常见类型不能漏
除了普通对象和数组,实际业务中常出现 Date、RegExp、Map、Set、Error 等内置类型。它们不能靠 for...in 或展开运算符复制,需单独判断并构造:
-
obj instanceof Date→new Date(obj.getTime()) -
obj instanceof RegExp→new RegExp(obj.source, obj.flags) -
obj instanceof Map→ 遍历obj.entries(),逐对深拷贝 key 和 value -
obj instanceof Set→ 用深拷贝后的值新建 Set
函数要不要拷贝?得看需求
函数本身是引用类型,但通常不参与数据逻辑变更。多数深拷贝实现默认跳过函数(typeof obj === 'function' 直接返回原函数)。如果确实需要“复制”函数(比如保留闭包行为),只能返回原引用——JavaScript 中函数无法真正克隆,所谓“拷贝”只是复用。











