深拷贝必须复刻属性描述符(writable、configurable、enumerable)而非仅复制值,需用Object.getOwnPropertyDescriptors获取全量自有描述符,递归处理value/get/set并保持this指向,最后用Object.defineProperties重建。

JavaScript 中深拷贝要真正复制对象的属性描述符(writable、configurable、enumerable),不能靠遍历赋值或 JSON 序列化——这些方式只复制值,完全丢弃元信息。关键在于绕过“读取-赋值”路径,直接采集并重建描述符本身。
用 Object.getOwnPropertyDescriptors 获取完整元信息
这是实现描述符复刻的第一步,也是不可替代的环节:
- 它能拿到所有自有属性(包括不可枚举属性、Symbol 键、getter/setter)的完整描述符对象,而
Object.keys()或for...in都会过滤掉不可枚举项和 Symbol - 返回结构是
{ propName: { value, get, set, writable, enumerable, configurable } },每个字段都原样保留原始定义 - 注意:它只处理自有属性,不包含原型链上的属性;如需继承结构,需额外用
Object.getPrototypeOf()逐层处理
递归处理 value / get / set,但保持 this 指向正确
拿到描述符后不能直接挂载,尤其访问器函数内部依赖 this,必须重定义逻辑:
- value:若为普通对象或数组,递归调用深拷贝;若为 Date、RegExp、Map、Set 等特殊类型,按对应规则克隆
-
get:新建 getter 函数,确保内部访问的是新对象上的同名属性(例如原
get() { return this._x * 2; },拷贝后应访问新对象的this._x) - set:同理,新建 setter,写入目标必须是新对象自身,而非原对象
- 函数本身通常不深拷贝(闭包无法复制),保留引用即可;如需隔离,可封装一层代理或注入上下文
用 Object.defineProperties 精确重建描述符
最后一步是把处理后的描述符批量定义到新对象上,这是还原 writable、configurable、enumerable 的唯一可靠方式:
立即学习“Java免费学习笔记(深入)”;
-
Object.defineProperties(target, descriptors)会严格按每个描述符设置属性特性 - 若原属性
enumerable: false,拷贝后仍不会出现在for...in或Object.keys()中 - 若原属性
configurable: false,拷贝后也无法被delete或再次修改描述符 - 若原属性
writable: false,拷贝后该属性值也无法被重新赋值
实际代码骨架示意
一个最小可行的硬核深拷贝函数核心逻辑如下:
function deepCloneWithDescriptors(obj) {
if (obj === null || typeof obj !== 'object') return obj;
<p>const target = Array.isArray(obj) ? [] : {};</p><p>// 1. 获取全量自有描述符
const descriptors = Object.getOwnPropertyDescriptors(obj);</p><p>// 2. 递归处理每个描述符的 value/get/set
const processed = {};
for (const [key, desc] of Object.entries(descriptors)) {
if ('value' in desc) {
processed[key] = { ...desc, value: deepCloneWithDescriptors(desc.value) };
} else if ('get' in desc || 'set' in desc) {
processed[key] = {
...desc,
get: desc.get ? function() { return this[key] ? deepCloneWithDescriptors(this[key]) : undefined; } : undefined,
set: desc.set ? function(val) { this[key] = deepCloneWithDescriptors(val); } : undefined
};
}
}</p><p>// 3. 批量定义,保留全部描述符特性
Object.defineProperties(target, processed);</p><p>return target;
}不复杂但容易忽略:描述符不是“附属品”,而是对象行为的底层契约。要真正 1:1 复刻,就得从描述符层面重建,而不是在值层面打补丁。


















