克隆对象时非标准内置属性丢失,本质是深拷贝未识别特殊类型构造机制;需对error、date、regexp、map、set、typedarray等分别处理,避免json或简单递归,并优先使用structuredclone或lodash.clonedeep。

克隆对象时非标准内置属性丢失,本质是深拷贝逻辑未覆盖目标对象的特殊构造机制或元数据。这不是“拷不全”的问题,而是“没认出它该被怎么拷”的问题。关键在识别类型、还原构造、保留元信息。
识别哪些属性属于“非标准内置”
所谓“非标准内置”,指那些不满足 typeof obj === 'object' 且非 null 的常规判断,但又不是基本类型(string/number/boolean/undefined/null)的对象,例如:
-
Error 实例:有
name、message、stack,但原型链和构造方式特殊 -
Date / RegExp:内部 [[PrimitiveValue]] 不可直接遍历,需用
new Date(obj.getTime())或new RegExp(obj)重建 -
Map / Set / WeakMap / WeakSet:键值对结构不可通过
for...in或Object.keys()获取 -
TypedArray(如 Uint8Array):有
buffer、byteOffset、length等只读属性,且底层共享 ArrayBuffer - Promise / GeneratorFunction / AsyncFunction:无法安全克隆,应明确标记为不可复制并原样透传或报错
手写克隆函数必须显式分支处理
不能依赖 JSON.parse(JSON.stringify()) 或简单递归遍历,必须对每类特殊对象做独立构造与填充:
-
Error:用
new obj.constructor(obj.message)创建新实例,再手动赋值name、stack(若存在)、cause(ES2022+) -
Date / RegExp:直接调用其构造函数,如
new Date(obj.getTime())、new RegExp(obj) -
Map / Set:用解构 + 映射方式重建:
new Map([...obj].map(([k, v]) => [deepClone(k), deepClone(v)])) -
TypedArray:先
new obj.constructor(obj),再检查是否需复制buffer(如需深隔离则obj.buffer.slice()) - 函数、Symbol、undefined:一律原样返回,不递归、不包装
避免缓存机制干扰元信息还原
使用 WeakMap 做循环引用检测时,注意它只缓存“对象引用 → 克隆结果”的映射,不影响属性复制逻辑。但以下两点极易出错:
- 不要在缓存前就修改源对象(如误加临时属性),否则副本会带入脏数据
- 对
Date、RegExp等轻量对象,无需进缓存表——它们无循环引用风险,且重建成本极低 - 若业务中自定义了带 Symbol 属性或不可枚举属性(如
Object.defineProperty(obj, 'id', { value: 1, enumerable: false })),需用Object.getOwnPropertyDescriptors()+Object.defineProperties()显式复制
生产环境建议优先用成熟方案
手写易漏、难维护。推荐按场景选择:
-
浏览器主线程 + 无函数需求:用
structuredClone()(现代浏览器支持),天然保留Date、Map、Set、ArrayBuffer等,但不支持function、undefined、RegExp(后者可降级兜底) -
Node.js 或需函数支持:用
lodash.cloneDeep(),已内置全部主流类型判断,且可通过customizer钩子扩展私有类型 -
高频、低延迟、强定制:封装一个带白名单的克隆器,只允许业务真正用到的类型(如仅
PlainObject、Date、Map),其余抛错,比“尽力而为”更可控










