支持原型链保留的深拷贝需用Object.create获取原对象原型并创建新对象,递归拷贝自有属性描述符,配合WeakMap处理循环引用,同时兼容Date、RegExp、Map、Set等内置类型。

JS 中通过原型链复制实现深拷贝,核心是既要递归复制所有嵌套数据,又要让新对象的 [[Prototype]] 指向原对象的原型,而不是默认的 Object.prototype。这和普通深拷贝不同——多数方法(如 JSON.parse(JSON.stringify())、structuredClone() 或 Lodash 的 cloneDeep)都会丢失原型信息,生成 plain object。
用 Object.create 保留原型关系
关键步骤是:先获取原对象的原型,再用 Object.create() 创建新对象,最后把自有属性(含 getter/setter、不可枚举属性等)复制过去。
- 用
Object.getPrototypeOf(obj)获取源对象原型,避免依赖已废弃的__proto__ - 调用
Object.create(proto)创建空对象,其内部[[Prototype]]已设为原原型 - 用
Object.getOwnPropertyDescriptors(obj)提取所有自有属性描述符(包括configurable、enumerable、writable、get/set) - 对每个属性值做深拷贝(递归处理对象/数组,新建 Date/RegExp/Map/Set 实例等),再用
Object.defineProperties(newObj, descriptors)批量定义
必须处理循环引用
原型链本身可能形成闭环(比如自定义类显式设置 Child.prototype.__proto__ = Parent.prototype,或对象间相互引用),不加防护会导致无限递归栈溢出。
- 使用
WeakMap缓存“源对象 → 拷贝对象”映射,每次进入递归前先查缓存 - 对函数、
undefined、Symbol等非可序列化类型,通常选择跳过或浅层复制(深拷贝函数语义模糊,一般不推荐) - 注意:
WeakMap键必须是对象,所以基础类型(string/number/null)无需缓存
兼容特殊内置类型
仅靠 Object.getOwnPropertyDescriptors 不足以还原 Date、RegExp、Map、Set 等实例行为,它们的内部状态需显式重建。
-
Date:用new Date(original.getTime())或new Date(original.toString()) -
RegExp:用new RegExp(original.source, original.flags) -
Map/Set:遍历键值对,分别深拷贝 key 和 value 后重新构造 -
Error、ArrayBuffer、TypedArray等也需单独分支判断并新建实例
注意事项与常见陷阱
这类手写方案虽灵活,但容易忽略细节导致行为偏差:
- 不可枚举属性(如
Object.defineProperty(obj, 'x', { enumerable: false }))会被for...in或扩展运算符忽略,但getOwnPropertyDescriptors可捕获——这点必须用对 - 原型上的方法不会被拷贝,只继承;若需“复制方法体”,属于代码生成范畴,不属于深拷贝职责
-
structuredClone在 Chrome 98+/Node.js 17+ 支持部分原型保留(如Date.prototype),但对自定义构造函数无效,不能替代手动方案 - 性能敏感场景慎用手写——每层都要查
WeakMap、提取描述符、定义属性,开销明显高于structuredClone或_.cloneDeep


















