工业级深拷贝需用WeakMap缓存解决循环引用,分类型处理Date、RegExp、Map、Set等特殊对象,还原Symbol键、不可枚举属性及原型链,并支持插件式策略扩展。

工业级深拷贝不能只靠 JSON.parse(JSON.stringify(obj)) 或简单递归,必须处理循环引用、不可枚举属性、特殊对象(Date、RegExp、Map、Set、ArrayBuffer 等)、原型链、Symbol 键、函数、以及性能与可扩展性。核心是用 WeakMap 缓存已拷贝对象 解决循环引用,并按类型分发处理逻辑。
支持循环引用:用 WeakMap 记录映射关系
若对象存在自引用或相互引用,朴素递归会无限栈溢出。解决方法是在进入每个对象前先查缓存,命中则直接返回已有拷贝,避免重复遍历。
- 使用
WeakMap(而非 Map)——键必须是对象,且不阻止垃圾回收,内存安全 - 缓存结构为
weakmap.set(source, clone),后续遇到同一 source 直接返回 clone - 注意:WeakMap 的 key 不能是 null、undefined 或原始值,恰好契合“只对引用类型做缓存”的语义
精确识别并分类处理各类内置对象
不同构造器实例需差异化拷贝策略,不能一概而用 Object.assign 或展开语法:
-
Date / RegExp:直接调用构造函数新建实例,如
new Date(obj.getTime())、new RegExp(obj.source, obj.flags) -
ArrayBuffer / TypedArray:用
obj.slice(0)或new obj.constructor(obj)复制底层二进制数据 - Map / Set:分别遍历 entries 或 values,递归拷贝 key 和 value(Map 的 key 可能也是引用类型,需深拷贝)
- Function:工业场景通常保留原函数(不拷贝闭包),可选择性跳过或浅拷贝(仅复制函数引用);若需“克隆”行为(如绑定新 this),应由用户显式配置
保留元信息:Symbol 键、不可枚举属性与原型
默认 Object.keys() 或 for...in 会丢失 Symbol 属性和不可枚举字段(如 obj.constructor),而 Object.getOwnPropertyNames() + Object.getOwnPropertySymbols() 可覆盖全部自有属性。
- 用
Object.getOwnPropertyDescriptors(obj)获取完整描述符(含writable、enumerable、configurable、get/set) - 通过
Object.defineProperties(clone, descriptors)精确还原属性特性 - 原型链用
Object.getPrototypeOf(obj)获取,再用Object.setPrototypeOf(clone, protoClone)设置(注意:null 原型需特殊处理)
可配置与可扩展:支持插件式类型处理器
硬编码所有类型判断难以维护。推荐设计为“策略注册”模式:
- 定义统一接口:
canHandle(obj): boolean和clone(obj, cache, cloneFn): any - 内置处理器按优先级注册(如 Date > RegExp > Map > Object),避免类型误判(例如某些类数组对象可能同时满足 Array.isArray 和 toStringTag === 'Array')
- 暴露
addHandler(typeCheck, handler)方法,供业务方注入自定义类(如 protobuf Message、Three.js Vector3)的拷贝逻辑 - 提供选项开关:是否拷贝原型、是否保留函数、是否冻结结果、是否记录拷贝路径(用于调试循环引用位置)
不复杂但容易忽略:真正的工业级深拷贝不是写得“最全”,而是边界清晰、错误可追踪、扩展无侵入、性能有保障(如对大型数组用 for 循环而非 map 减少函数调用开销)。缓存、类型分发、元信息还原、策略扩展这四点齐备,就具备了在中后台系统、低代码平台、状态持久化等场景落地的能力。


















