深拷贝需用 object.getownpropertydescriptor 获取属性描述符以保留元数据。因普通遍历丢失不可枚举、访问器、symbol 属性及 configurable/writable 状态,须合并 object.getownpropertynames 与 object.getownpropertysymbols 的键并逐个定义;访问器属性须原样复制 get/set 函数,数据属性则递归深拷贝 value;循环引用需 weakmap 防止栈溢出,不可配置属性还原前应校验兼容性。

JavaScript 中深拷贝通常关注值的递归复制,但若需完整保留对象的元数据(如 configurable、enumerable、writable、get/set),就必须显式获取并还原属性描述符——这超出了 JSON.parse(JSON.stringify()) 或简单递归赋值的能力。
为什么要用 Object.getOwnPropertyDescriptor()
普通遍历(如 for...in 或 Object.keys())只能访问可枚举的自有属性值,会丢失:
- 不可枚举属性(如
Object.prototype.toString) - 访问器属性(
get/set)及其定义逻辑 -
configurable和writable状态 - Symbol 类型的键名属性
而 Object.getOwnPropertyDescriptor(obj, key) 能精确读取单个属性的完整描述符;配合 Object.getOwnPropertyDescriptors(obj)(ES2017+)可一次性获取所有自有属性描述符,是深拷贝保元信息的基础。
如何获取并还原所有自有属性(含 Symbol 和不可枚举)
仅靠 Object.keys() 或 Object.getOwnPropertyNames() 不够,需合并字符串键与 Symbol 键:
- 用
Object.getOwnPropertyNames(obj)获取所有字符串键名 - 用
Object.getOwnPropertySymbols(obj)获取所有 Symbol 键名 - 合并后对每个 key 调用
Object.getOwnPropertyDescriptor(obj, key) - 用
Object.defineProperty(target, key, descriptor)逐个还原
例如:
注意访问器属性与数据属性的处理差异
描述符中若含 get 或 set,说明是访问器属性,此时不能直接拷贝 value,否则会丢失逻辑:
- 数据属性:有
value和writable,深拷贝时需递归处理value - 访问器属性:有
get/set函数,应原样复制函数引用(除非需绑定新上下文) - 混合类型需分支判断:
'value' in desc ? 'data' : 'accessor'
错误做法是统一用 target[key] = source[key] —— 这会触发 get 并赋值结果,彻底破坏访问器语义。
循环引用与不可配置属性的边界情况
获取描述符本身不处理循环引用,但还原时可能出错:
- 若原始属性
configurable: false,后续对同一 key 再次调用defineProperty可能抛错(尤其尝试修改configurable或从数据变访问器) - 循环引用对象在获取描述符阶段无影响,但在递归深拷贝
value时需用 WeakMap 记录已处理对象 -
Object.getOwnPropertyDescriptors()返回的是浅拷贝描述符对象,其get/set函数仍需单独处理绑定或克隆
安全策略通常是:只还原可还原的描述符字段,对不可配置属性跳过二次定义,或提前校验兼容性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











