深拷贝冻结/不可扩展对象需先在目标对象上设置对应状态再赋值。具体包括:先判断源对象状态并初始化目标对象;用getOwnPropertyDescriptors获取自有属性描述符;对特殊类型如数组、Date等单独处理;循环引用时缓存已设状态的目标对象。

深拷贝不可扩展(Object.preventExtensions)或已冻结(Object.freeze)的对象时,关键不是“跳过”或“报错”,而是**在目标对象上提前设置对应状态**,再进行属性赋值。否则直接 Object.assign 或递归赋值会失败(静默或抛错),导致拷贝不完整或中断。
先判断源对象状态,再初始化目标对象
不能等拷贝完再调用 Object.freeze —— 那样无法添加新属性。必须在创建空对象后、开始复制属性前,就根据源对象的状态对目标对象执行对应操作:
-
若源对象被冻结(
Object.isFrozen(obj)为true):先用Object.freeze(Object.create(null))创建一个空冻结对象,再逐个设置自有可写属性(注意:冻结对象的属性仍可写,除非属性本身也被设为只读); -
若仅不可扩展(
Object.isExtensible(obj)为false,但未冻结):用Object.preventExtensions({})创建空不可扩展对象; -
若被密封(
Object.isSealed(obj)):需同时防止扩展 + 将所有自有属性设为不可配置(Object.defineProperty配合configurable: false)。
遍历自有属性,跳过不可枚举和继承属性
使用 Object.getOwnPropertyDescriptors(obj) 获取所有自有属性描述符(包括不可枚举、getter/setter、writable/configurable 等),避免遗漏。对每个描述符:
- 若属性是
get或set,原样定义到目标对象(深拷贝 getter 内部值需单独递归处理); - 若属性值是对象且可深拷贝,递归调用拷贝函数;
- 若属性不可写(
writable: false),用Object.defineProperty定义,避免赋值失败; - 忽略原型链上的属性(
hasOwnProperty或getOwnPropertyDescriptor已保证只处理自有属性)。
特殊对象类型要单独处理
数组、Date、RegExp、Map、Set 等不能靠 Object.getOwnPropertyDescriptors 直接还原,需先识别类型,再构造对应实例并设置状态:
立即学习“Java免费学习笔记(深入)”;
- 数组:用
Array.from(arr)或new Array(arr.length)创建,再根据源是否冻结/密封,对新数组调用Object.freeze或Object.preventExtensions; - Date / RegExp:直接
new Date(obj)或new RegExp(obj)构造,它们本身不可扩展,无需额外冻结; - Map / Set:遍历
obj.entries()或obj.values(),逐个set或add,最后对新 Map/Set 执行相同状态设置(如Object.freeze(map)对 Map 无效,但可对其包装对象冻结)。
循环引用与状态一致性要同步检查
如果源对象存在循环引用,且其中某个节点已被冻结,那么在缓存中记录的必须是**已冻结的目标对象**,后续再次访问时直接返回它,而不是尝试重新冻结——重复冻结无副作用,但需确保该对象已具备全部预期状态(不可扩展 + 属性不可配置等)。建议在缓存映射(WeakMap)中存储的是「已完成状态设置的目标对象」,而非中间态对象。


















