深拷贝需显式保留属性描述符,object.getownpropertydescriptors+object.defineproperties可单层复制,递归处理嵌套对象并重建描述符;常见方法如json.parse(json.stringify())、structuredclone()等均丢失描述符。

深拷贝时保留属性描述符(如 writable、enumerable、configurable,以及 get/set 访问器)不是默认行为,常规方法(如 JSON.parse(JSON.stringify()) 或 structuredClone())会丢失这些元信息,只保留值。要真正“深度”还原对象的完整语义,必须显式读取并重定义每个属性的描述符。
用 Object.getOwnPropertyDescriptors + Object.defineProperties
这是最直接、兼容性较好(ES2017+)的方式,能完整复制自有属性的描述符(包括数据属性和访问器属性),但仅限于**单层**对象:
- 调用
Object.getOwnPropertyDescriptors(obj)获取所有自有属性的完整描述符对象 - 创建新对象(如
{}或Object.create(Object.getPrototypeOf(obj))以保留原型) - 用
Object.defineProperties(newObj, descriptors)将描述符批量应用到新对象上
示例:
const original = {};
Object.defineProperty(original, 'x', {
value: 42,
writable: false,
enumerable: true,
configurable: false
});
Object.defineProperty(original, 'y', {
get() { return this.x * 2; },
enumerable: false,
configurable: true
});
<p>const clone = Object.create(Object.getPrototypeOf(original));
Object.defineProperties(clone, Object.getOwnPropertyDescriptors(original));
// clone.x 不可写,clone.y 是访问器,且原型链一致</p>
递归处理嵌套对象 + 描述符保持
若对象有多层嵌套,且每一层都需要保留描述符(不只是值),需在递归过程中对每个对象节点都执行描述符拷贝:
- 遇到普通对象或数组,先创建对应类型的新容器(
{}或[]) - 获取该对象所有自有属性名(
Object.getOwnPropertyNames()和Object.getOwnPropertySymbols()) - 对每个 key,用
Object.getOwnPropertyDescriptor()取描述符 - 若描述符中
value是对象/数组,递归深拷贝其值;若为get/set,直接复制函数引用(注意:函数本身无法深拷贝,通常保留原引用) - 用
Object.defineProperty()在目标对象上重建该属性
注意:访问器属性(get/set)中的 this 指向新对象,无需额外处理,但内部逻辑若依赖外部闭包则行为不变。
避开常见陷阱
很多看似“深拷贝”的操作其实完全忽略描述符:
-
Object.assign()和展开运算符{...obj}:只复制可枚举的自有属性值,丢弃writable、configurable等,且不复制 Symbol 属性和访问器 -
JSON.parse(JSON.stringify()):连属性名都只保留字符串键,彻底丢失所有描述符、Symbol、函数、undefined、Date、RegExp 等 -
structuredClone():虽支持循环引用和多种内置类型,但不保留任何属性描述符,所有属性都变成默认writable: true, enumerable: true, configurable: true
因此,当业务逻辑依赖 Object.defineProperty 设置的不可写、不可枚举或访问器行为时,必须主动使用描述符 API,不能依赖通用深拷贝工具。
是否需要拷贝原型链?
严格意义上的“完整对象拷贝”应包含原型:
- 用
Object.getPrototypeOf(obj)获取原对象原型 - 用
Object.create(proto)创建继承相同原型的新空对象 - 再用
Object.defineProperties()填充实例属性(含描述符)
如果不关心原型,仅需实例属性,跳过 Object.create() 步骤即可。多数场景下,深拷贝关注的是数据结构而非继承关系,但涉及自定义类实例时需特别留意。











