JavaScript深拷贝需用Object.getOwnPropertyDescriptors获取全部自有属性描述符,结合Object.getOwnPropertyNames、Object.getOwnPropertySymbols、Object.defineProperties及WeakMap处理不可枚举属性、Symbol键、访问器、原型链和循环引用。

JavaScript 中默认的浅拷贝方法(如 Object.assign()、扩展运算符 {...obj} 或 JSON.parse(JSON.stringify()))都**无法复制不可枚举属性**,因为它们只处理可枚举自有属性。要完整复制包含不可枚举属性、访问器属性(getter/setter)、原型链、Symbol 键甚至循环引用的复杂对象,需手动遍历所有属性特性。
用 Object.getOwnPropertyDescriptors() 获取全部自有属性描述符
这是关键一步:它能拿到包括 enumerable: false、configurable、writable、get/set 在内的完整定义,不遗漏不可枚举属性或访问器。
- 配合
Object.getOwnPropertyNames()(获取字符串键,含不可枚举)和Object.getOwnPropertySymbols()(获取 Symbol 键),覆盖所有自有属性 - 对每个键调用
Object.getOwnPropertyDescriptor(obj, key),得到完整 descriptor - 用
Object.defineProperties(target, descriptors)将所有 descriptor 复制到新对象上
显式处理原型链和不可枚举继承属性
不可枚举属性也可能来自原型(比如 Array.prototype 上的某些方法被设为不可枚举)。若需保留原型关系:
- 用
Object.getPrototypeOf(obj)获取原对象原型 - 用
Object.create(proto)创建新对象并指定相同原型 - 注意:原型上的属性本身不会被
getOwnPropertyDescriptors捕获,如需深拷贝整个原型链,需递归处理(但通常只复制自有部分更安全)
处理循环引用与嵌套对象
上述方法只是“自有属性级”的深拷贝起点。若对象内部有嵌套引用(包括循环),需额外追踪已拷贝对象:
立即学习“Java免费学习笔记(深入)”;
- 维护一个
WeakMap映射:原始对象 → 已创建副本 - 每次进入对象前先查 map,命中则直接返回已有副本,避免无限递归
- 对每个属性值判断类型:普通值直接赋值;对象/数组则递归调用拷贝函数;函数通常直接引用(除非明确需要绑定新上下文)
一个实用的完整实现示例
以下函数支持不可枚举属性、Symbol 键、访问器、原型继承,并防止循环引用:
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
let clone;
const proto = Object.getPrototypeOf(obj);
if (Array.isArray(obj)) {
clone = [];
} else if (obj.constructor === Object) {
clone = Object.create(proto);
} else {
// 保留构造函数(如 Date、RegExp 等)
clone = new obj.constructor();
}
seen.set(obj, clone);
// 合并字符串键 + Symbol 键
const keys = [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)];
const descriptors = {};
for (const key of keys) {
descriptors[key] = Object.getOwnPropertyDescriptor(obj, key);
}
Object.defineProperties(clone, descriptors);
// 递归处理属性值(跳过 getter/setter,只处理 data 属性值)
for (const key of keys) {
const desc = descriptors[key];
if ('value' in desc && desc.enumerable !== false) {
clone[key] = deepClone(desc.value, seen);
}
}
return clone;
}
⚠️ 注意:该实现会递归克隆 value 类型属性,但跳过 get/set 的执行(保留原访问器逻辑)。如需克隆 getter 返回值,需额外调用并缓存结果。


















