深拷贝默认不处理原型链属性,推荐做法是用object.getprototypeof获取原型、object.create重建原型链,再深拷贝自有属性;需复制原型属性时应遍历原型链并用object.getownpropertydescriptors+defineproperties处理。

深拷贝默认不处理原型链上的属性,因为它们不属于对象的“自有属性”。要保留原型链结构(即新对象的 [[Prototype]] 指向原对象的原型),关键不是“拷贝原型上的属性”,而是“重建正确的原型链关系”,同时按需决定是否复制原型上的可枚举/不可枚举属性。
只保留原型链,不复制原型属性
这是最常见且推荐的做法:新对象拥有和原对象相同的原型,但自身不包含原型上的任何属性(符合 JavaScript 原型继承的设计本意)。
- 用
Object.getPrototypeOf(obj)获取原对象的原型 - 用
Object.create(proto)创建新对象,其[[Prototype]]直接指向该原型 - 再把原对象的自有属性(包括不可枚举、getter/setter)深拷贝到这个新对象上
需要复制原型上的属性时怎么办
这种情况较少见,通常意味着你在模拟某种“扁平化继承”或特殊克隆语义。注意:这会破坏原型链的动态性(后续在原型上添加方法,新对象不会自动获得)。
- 遍历原型链(
Object.getPrototypeOf循环)获取每一层原型 - 对每层原型调用
Object.getOwnPropertyDescriptors() - 用
Object.defineProperties()把这些描述符逐层定义到新对象上(或合并后一次性定义) - 必须手动控制顺序和覆盖逻辑,避免 descriptor 冲突(如多个同名 setter)
为什么不能依赖 constructor 或 for...in
constructor 不可靠——它可能被改写,且函数本身也是对象,其原型是 Function.prototype,不是你想要的业务原型。
for...in 会遍历整个原型链上的可枚举属性,无法区分“来自哪一层原型”,也容易漏掉不可枚举属性(如 hasOwnProperty)和 symbol 键。
正确做法始终围绕 Object.getPrototypeOf() + Object.getOwnPropertyDescriptors() + Object.defineProperties() 这组 API。
实际代码中要同步处理的配套点
仅设置原型还不够,完整实现还需:
- 用
WeakMap缓存已拷贝对象,解决循环引用,且缓存值必须是已设置好原型的新对象 - 对
Date、RegExp、Map、Set等内置类型单独分支处理,确保它们的新实例也继承对应原型(如new Date()自动继承Date.prototype) - 跳过原型链上的属性遍历,只处理
Object.keys()和Object.getOwnPropertySymbols()返回的自有键
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











