JavaScript自定义深拷贝保留原型链的核心是用Object.getPrototypeOf()获取原对象原型,再通过Object.create()创建新对象并正确设置[[Prototype]],同时处理内置类型、循环引用及不可枚举属性,避免依赖不可靠的constructor。

JavaScript 中实现自定义深拷贝时,若需保留原型链继承关系,核心在于:拷贝后的新对象必须拥有与原对象相同的原型([[Prototype]]),而不能默认指向 Object.prototype。这需要显式设置新对象的原型,且不能依赖 JSON.stringify 或简单递归赋值——它们会丢失原型信息。
用 Object.create 重建原型链
最直接的方式是先获取原对象的原型(Object.getPrototypeOf(obj)),再用 Object.create() 创建一个具有相同原型的新空对象,然后逐个属性深拷贝过去。
- 对普通对象、数组等可枚举属性递归深拷贝,但跳过不可枚举属性和原型上的属性(除非你明确想复制)
- 使用
Object.getOwnPropertyDescriptors(obj)获取所有自有属性(含 getter/setter、writable、enumerable 等),再用Object.defineProperties()复制到新对象上 - 注意:
Object.create(proto)创建的对象自身为空,不会继承原型上的属性,只保留原型链结构
处理函数、Date、RegExp 等内置类型
这些类型实例通常有特定原型(如 Date.prototype、RegExp.prototype),直接 new Date(obj) 或 new RegExp(obj) 可以保留原型,但需判断类型并分支处理:
-
obj instanceof Date→new Date(obj.getTime()) -
obj instanceof RegExp→new RegExp(obj.source, obj.flags) -
obj instanceof Map / Set→ 构造新实例并遍历赋值(new Map(obj)支持,但注意深拷贝其键值) - 普通函数一般不深拷贝(函数体不可序列化),可选择直接引用或浅拷贝;若需“复制”函数,通常意味着重新定义,此时原型链无关(函数本身是
Function.prototype的实例)
避免循环引用与保持原型一致性
循环引用会导致无限递归,需用 WeakMap 缓存已拷贝对象;同时要确保缓存 key 是原对象,value 是对应的新对象(其原型已正确设置),后续遇到同一对象时直接返回缓存结果。
立即学习“Java免费学习笔记(深入)”;
- WeakMap 不影响垃圾回收,适合做映射缓存
- 每次创建新对象前,先查缓存;命中则直接返回,避免重复设置原型或拷贝
- 特别注意:不能用原对象的
constructor构造新对象(如obj.constructor()),因为构造函数可能被篡改或无意义(如 Object.create(null) 没有 constructor)
不推荐依赖 constructor 的原因
很多对象的 constructor 并不可靠:Object.create(null) 的对象没有 constructor;类实例若原型被修改,constructor 可能指向错误;箭头函数、内置对象(如 Array)的 constructor 调用行为也不统一。因此应始终优先使用 Object.getPrototypeOf() 获取真实原型,而非试图通过 constructor 重建。
不复杂但容易忽略


















