手写深拷贝保留原型链的关键是用Object.create(Object.getPrototypeOf(obj))创建新对象,再递归拷贝自有属性及描述符;需特殊处理Date、RegExp、Array、Map、Set等内置类型,并用WeakMap解决循环引用。

手写深拷贝时保留原型链,关键不是复制属性,而是让新对象的 __proto__(或 [[Prototype]])指向源对象的原型。原生 JSON.parse(JSON.stringify()) 和多数简易递归实现会丢失原型,因为它们只处理可枚举自有属性,且最终创建的是 Object 实例。
用 Object.create() 显式继承原型
最直接的方式:先获取源对象的原型,再用 Object.create() 创建新对象,使其原型链与源对象一致,最后递归拷贝自有属性。
- 通过
Object.getPrototypeOf(obj)获取源对象的原型(兼容性好,比obj.__proto__更规范) - 用
Object.create(proto)创建一个空对象,其[[Prototype]]指向该原型 - 遍历源对象的 自有属性(
Object.getOwnPropertyNames()或Reflect.ownKeys()),包括不可枚举属性和 symbol 键 - 对每个属性,用
Object.getOwnPropertyDescriptor()获取完整描述符(含value、writable、enumerable、configurable、get、set),再递归深拷贝value或保留访问器
处理函数、Date、RegExp、Map、Set 等内置类型
单纯继承原型还不够——像 Date、RegExp 实例的原型是 Date.prototype、RegExp.prototype,但它们的内部状态(如时间戳、正则表达式字符串)需单独提取并重建。
-
Date→new Date(obj.getTime()) -
RegExp→new RegExp(obj.source, obj.flags)(注意lastIndex是否需要保留) -
Array→ 用Array.isArray()判断,优先用Array.from()或map()深拷贝,再确保新数组的原型仍是Array.prototype(Object.create(Array.prototype)即可) -
Map/Set→ 需遍历键值对,分别深拷贝 key 和 value;新建实例后逐个set()或add(),原型自然继承
保留访问器属性(getter/setter)
如果源对象某属性是访问器(get/set),不能只拷贝返回值,而应原样复刻描述符。否则会丢失响应逻辑或副作用。
立即学习“Java免费学习笔记(深入)”;
- 用
Object.getOwnPropertyDescriptor()获取描述符 - 若
descriptor.get或descriptor.set存在,直接赋给新对象(函数本身无需深拷贝,它是不可变的引用) - 若为数据属性(
value),才对value递归深拷贝 - 用
Object.defineProperty()将描述符定义到新对象上
避免循环引用与构造函数干扰
保留原型不等于调用构造函数。例如 new obj.constructor() 可能出错(constructor 不可靠、可能不存在、可能有副作用)。应始终以原型为锚点,而非构造器。
- 用
WeakMap缓存已拷贝的对象,检测并解决循环引用(键为源对象,值为对应的新对象) - 不要依赖
obj.constructor创建实例,它可能被改写或指向Object - 对自定义类实例,只要原型链完整、属性拷贝正确,行为就与原对象一致


















