深拷贝无需复制原型链,而应通过object.create()或构造器还原继承关系;因原型含方法、循环引用及内置对象特殊性,直接拷贝会导致错误。

手写深拷贝时,原型链本身不需要被“复制”,而是需要在创建新对象时正确继承原对象的原型。JavaScript 的深拷贝关注的是数据(属性值)的递归复制,而原型链属于对象的“行为契约”,应通过 Object.create() 或构造函数机制还原,而非逐层拷贝原型对象本身。
为什么不能“拷贝原型对象”?
原型对象通常包含方法、共享配置或不可枚举属性,它们不是数据,也不该被深拷贝:
- 方法是函数引用,拷贝函数体既无必要也易出错(闭包、this 绑定丢失);
- 原型链可能含循环引用(如
Array.prototype.__proto__ === Object.prototype),盲目遍历会死循环; - 内置对象(
Date、RegExp、Map等)的原型来自原生构造器,必须用对应构造方式重建,不能靠复制属性模拟。
识别原型并还原继承关系的关键步骤
核心是获取原对象的“原型意图”,即它本应由哪个构造器创建,并用该构造器或 Object.create() 初始化新实例:
- 用
Object.getPrototypeOf(obj)获取当前原型(非构造器,是 prototype 对象); - 用
obj.constructor或Object.prototype.toString.call(obj)判断类型(注意:constructor可被篡改,优先用toString); - 对普通对象(
[object Object])且非 null,用Object.create(Object.getPrototypeOf(obj))创建新空对象; - 对内置类型(
Date、RegExp、Array等),直接调用对应构造器(如new Date(obj)),它们内部已处理原型继承; - 对自定义类实例,若需保留原型链,可沿用其 constructor 并用
Object.create(obj.constructor.prototype),再复制自有属性。
一个兼顾原型继承的深拷贝简化实现
以下代码片段展示如何在递归拷贝中保留原型结构(忽略循环引用处理等完整逻辑,聚焦原型部分):
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
// 识别内置类型并直接构造
const tag = Object.prototype.toString.call(obj);
switch (tag) {
case '[object Date]': return new Date(obj);
case '[object RegExp]': return new RegExp(obj);
case '[object Array]': return obj.map(item => deepClone(item));
case '[object Map]': return new Map([...obj].map(([k, v]) => [k, deepClone(v)]));
case '[object Set]': return new Set([...obj].map(item => deepClone(item)));
}
// 普通对象或自定义对象:用原原型创建新对象
const proto = Object.getPrototypeOf(obj);
const cloned = Object.create(proto); // 关键:继承原原型
// 仅复制自有可枚举属性(避免污染原型)
for (const key of Object.keys(obj)) {
cloned[key] = deepClone(obj[key]);
}
return cloned;
}
注意事项和边界情况
实际工程中还需考虑:
-
不可枚举属性与 symbol 键:用
Object.getOwnPropertyNames()+Object.getOwnPropertySymbols()补充复制; -
访问器属性(getter/setter):需用
Object.getOwnPropertyDescriptor()获取描述符,再用Object.defineProperty()定义到新对象; -
冻结/密封对象:拷贝后是否保持 same state?需在克隆后调用
Object.freeze()等; -
函数对象:一般不深拷贝(保留引用),除非明确需要序列化+反序列化(如用
eval或Function构造,但极不推荐)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











