JavaScript中实现支持描述符保留的深拷贝,关键在于用Object.getOwnPropertyDescriptors获取所有自有属性描述符,递归深拷贝value/get/set,再用Object.defineProperties重建;需处理循环引用、访问器this指向、Symbol键、不可枚举属性及Date/RegExp/Map/Set等特殊对象。
JavaScript 中实现支持描述符保留的深拷贝,关键在于不能只复制属性值,还要完整还原 Object.defineProperty 所定义的所有特性(如 enumerable、writable、configurable、get/set)。JSON 序列化、structuredClone 或简单递归赋值都无法保留这些元信息。
这是最直接可控的方式:先用 Object.getOwnPropertyDescriptors 获取源对象所有自有属性的完整描述符,再对每个描述符中的 value 或 get/set 函数做深拷贝,最后用 Object.defineProperties 在新对象上重建。
get/set,需重新包装——内部访问的 this 要指向新对象,因此不能直接赋值,要重写访问器逻辑直接复制 get 函数会导致它内部的 this 仍指向原对象。正确做法是:在新对象上定义新的访问器,让 get 从新对象读取对应键(若目标是普通值),或调用深拷贝后的 getter 函数(若 getter 本身依赖其他状态)。
{ get() { return this._x * 2; } },拷贝后应生成 { get() { return this._x * 2; } },其中 this._x 是新对象上的深拷贝值toString()),并在新上下文中执行——但这有局限(无法处理闭包变量);生产环境建议明确约定访问器不依赖外部闭包Object.getOwnPropertyDescriptors 天然支持 Symbol 类型的键和不可枚举属性,无需额外处理。但要注意:for...in 和 Object.keys 会忽略它们,必须坚持使用该 API 才能保全。
立即学习“Java免费学习笔记(深入)”;
Object.getOwnPropertyDescriptors(obj),而不是 Object.keys(obj)
enumerable: false)在拷贝后也保持不可枚举,不影响遍历行为Date、RegExp、Map、Set、ArrayBuffer 等类型需单独判断并实例化。它们的描述符可能包含内部槽(internal slots),无法通过 getOwnPropertyDescriptors 完全暴露。例如:
new Date() 的 [[DateValue]] 不可枚举也不可配置,但可通过 getTime() 提取后重建RegExp 实例的 source 和 flags 可读,用 new RegExp(desc.source, desc.flags) 构造Map/Set 需遍历 entries() 或 values(),对键值对分别深拷贝后再填入新实例不复杂但容易忽略:深拷贝描述符的本质不是“复制数据”,而是“复现定义行为”。只要每一步都基于 defineProperty 级别的控制,并小心处理函数、循环引用和宿主对象,就能可靠保留所有元信息。