深拷贝会丢失属性描述符,导致get/set失效、赋值静默、遍历遗漏;JSON.parse/stringify、structuredClone、Object.assign、展开运算符及多数手写递归拷贝均不保留描述符。

深拷贝后属性描述符(如 getter、setter、enumerable、writable、configurable)丢失,会导致对象行为异常——比如取值不触发 get、赋值静默失败、遍历时漏掉关键字段。这不是“拷得不够深”,而是多数深拷贝方案默认只处理属性值,跳过元信息。
哪些深拷贝方式会丢描述符
以下操作均无法保留原始描述符:
-
JSON.parse(JSON.stringify(obj)):完全剥离所有元信息,只保留可序列化的键值对 -
structuredClone(obj)(现代浏览器):支持Map/Set等,但明确不复制描述符 -
Object.assign({}, obj)或展开运算符{...obj}:仅复制可枚举自有属性,且是浅拷贝 - 大多数手写递归深拷贝函数:若未显式调用
Object.getOwnPropertyDescriptor和Object.defineProperty,就无法重建描述符
如何检测描述符是否丢失
别等出问题才查,主动验证关键属性:
- 对比原始对象与拷贝对象的属性定义:
Object.getOwnPropertyDescriptor(original, 'key')vsObject.getOwnPropertyDescriptor(copy, 'key') - 检查
get是否还存在:typeof Object.getOwnPropertyDescriptor(copy, 'key')?.get === 'function' - 测试赋值是否受
set控制:
给copy.key = newValue后,观察是否有预期副作用(如日志、计算更新)
可靠修复方案:手动重建描述符
核心思路是:先深拷贝值,再用原始描述符重新定义属性。
- 对单个属性:获取原始描述符 → 深拷贝其
value或get/set函数(注意函数本身不能深拷贝,需原样引用或包装)→ 用Object.defineProperty注入新对象 - 对整个对象:遍历
Object.getOwnPropertyDescriptors(original),逐个应用到拷贝对象上
示例:const copy = {}; Object.getOwnPropertyDescriptors(original).forEach(([key, desc]) => { const newDesc = { ...desc }; if (desc.value !== undefined) newDesc.value = deepClone(desc.value); Object.defineProperty(copy, key, newDesc); }); - 若使用
lodash.cloneDeep,它不支持描述符;但可配合_.assignInWith自定义处理逻辑,把描述符逻辑注入进去
更健壮的设计建议
依赖描述符的行为,在拷贝场景下本身就脆弱。优先考虑替代方案:
- 把含
get/set的逻辑封装进类方法中,而不是靠属性访问触发 - 用
Proxy代理对象,将响应式逻辑与数据解耦,拷贝时只复制原始数据,代理层单独重建 - 对必须保留描述符的关键对象,避免通用深拷贝,改用构造函数新建实例 + 显式赋值关键字段

















