核心是用weakmap缓存已克隆对象以避免重复递归,查到即返、查不到才构造并缓存;weakmap键为弱引用,防内存泄漏;需跨递归复用同一map实例,现代环境优先用structuredclone()。

核心是用 Map(或更推荐的 WeakMap)缓存已克隆对象,让每次查找旧对象对应的新副本耗时稳定在 O(1)。关键不在“快”,而在“不重复递归”——查到就返回,查不到才构造并缓存。
为什么选 Map / WeakMap 而不是普通对象?
普通对象作缓存键只能是字符串,而对象引用无法自动转成唯一字符串标识;Map 和 WeakMap 支持任意值(包括对象)为键,且内部哈希实现保证平均 O(1) 查找。
- WeakMap 更优:键是弱引用,不阻止垃圾回收,适合长期运行、对象频繁创建销毁的场景
- Map 也可用,但需注意手动管理生命周期,否则易内存泄漏
- 不能用 {} 或 new Map() 作为函数默认参数——每次调用都新建,缓存失效
手写 deepClone 的关键步骤
每一步都围绕“查—占—填”逻辑展开:
- 先判断是否为基础类型(null、undefined、string、number、boolean、symbol、bigint、function)→ 直接返回,不进缓存
- 查缓存:
map.has(obj)→ 命中则return map.get(obj) - 未命中则立即占位:
const cloned = Array.isArray(obj) ? [] : {},然后map.set(obj, cloned) - 再递归填充属性(对 key/value 分别 deepClone),确保子对象也走同样流程
必须显式支持的内置类型
只处理 Object 和 Array 会漏掉真实业务中的 Map、Set、Date、RegExp、Error 等,导致结构错乱或报错:
-
Date / RegExp:用
new Date(obj)或new RegExp(obj)构造新实例,再缓存 - Map / Set:新建后,遍历其 entries 或 values,对每个元素递归克隆
-
Error:新建
new obj.constructor(obj.message),复制name、stack等可枚举属性 -
ArrayBuffer / TypedArray:用
obj.slice()或new obj.constructor(obj)复制底层数据
调用方式决定缓存是否生效
缓存实例必须跨递归层级复用,否则循环引用依然爆栈:
- ❌ 错误写法:
function deepClone(obj, map = new WeakMap())—— 每次调用新建 WeakMap - ✅ 推荐写法一(工厂模式):
const cloner = createCloner(); cloner(obj1); cloner(obj2); - ✅ 推荐写法二(显式传入):
const map = new WeakMap(); deepClone(obj1, map); deepClone(obj2, map);
现代环境优先考虑 structuredClone() —— 它底层就是基于结构化克隆算法,天然支持循环引用与多数内置类型,一行代码解决,无需手写。手写主要用于学习原理、定制行为(如保留函数、处理自定义类)或兼容老环境。











