深拷贝丢失原型是因为标准方法只复制自有可枚举属性,不处理[[prototype]]链接、不可枚举属性、symbol键及访问器;需用object.create()设原型,object.getownpropertydescriptors()拷贝所有属性描述符,并递归处理嵌套对象。

JavaScript 中通过原型链实现深拷贝时,原型丢失是因为大多数深拷贝方法(如 JSON.parse(JSON.stringify())、结构化克隆、或手动递归遍历)只处理对象的**自有属性和可枚举属性**,而不会复制构造函数、prototype、不可枚举属性、访问器属性(get/set)、Symbol 键,更不会重建原型链关系。
为什么深拷贝会丢失原型
原型链本质是对象内部的 [[Prototype]] 链接(可通过 Object.getPrototypeOf() 读取,Object.setPrototypeOf() 设置),但标准深拷贝不保留它:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
JSON 方法完全无视原型:
JSON.stringify()只序列化可枚举的、键为字符串、值为基本类型或普通对象/数组的自有属性;函数、undefined、Symbol、Date、RegExp、Map、Set等都会被忽略或转成空对象/字符串,原型自然消失。 -
结构化克隆(
structuredClone)有限支持:虽比 JSON 强(支持Date、RegExp、Map、Set、ArrayBuffer等),但它仍只克隆“数据”,不复制构造函数或原型。克隆结果总是Object实例,原型固定为Object.prototype。 -
手动递归拷贝通常只遍历
for...in或Object.keys():这些方法不包含不可枚举属性(如constructor、__proto__),也不检查源对象的[[Prototype]],因此新对象默认继承Object.prototype。
如何在深拷贝中保留原型
必须显式获取并设置原型。核心思路是:先创建一个空对象,将其原型设为原对象的原型,再把原对象的可枚举和不可枚举自有属性(包括 Symbol 键)逐个拷贝过去。
-
用
Object.create()创建带指定原型的对象:const cloned = Object.create(Object.getPrototypeOf(original)); -
用
Object.getOwnPropertyDescriptors()获取所有自有属性描述符(含不可枚举、get/set、configurable等):Object.defineProperties(cloned, Object.getOwnPropertyDescriptors(original)); -
注意 Symbol 属性需单独处理:因为
getOwnPropertyDescriptors已包含 Symbol 键,所以上一步已覆盖;但若需兼容旧环境,可用Object.getOwnPropertySymbols()补充。 -
对嵌套对象递归执行相同逻辑:遇到对象或数组,判断其是否为“普通对象”(即
Object.prototype.toString.call(x) === '[object Object]'),再决定是否递归并保留原型;否则按原类型构造(如new original.constructor(...),但需确保构造函数安全可用)。
一个保留原型的简易深拷贝示例
以下函数仅处理普通对象、数组及基础类型,保留原型与属性特性(不含循环引用检测):
function deepCloneWithPrototype(obj) {
if (obj === null || typeof obj !== 'object') return obj;
// 区分 Array 和普通对象(也可扩展 Date/RegExp 等)
const Ctor = obj.constructor;
let cloned;
if (Array.isArray(obj)) {
cloned = [];
Object.setPrototypeOf(cloned, Object.getPrototypeOf(obj));
} else {
cloned = Object.create(Object.getPrototypeOf(obj));
}
// 拷贝所有自有属性(含不可枚举、Symbol、get/set)
const descriptors = Object.getOwnPropertyDescriptors(obj);
Object.defineProperties(cloned, descriptors);
// 递归处理属性值
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepCloneWithPrototype(obj[key]);
}
}
// 补 Symbol 键(getOwnPropertyDescriptors 已包含,此步冗余但显式)
Object.getOwnPropertySymbols(obj).forEach(sym => {
cloned[sym] = deepCloneWithPrototype(obj[sym]);
});
return cloned;
}
实际使用中的注意事项
-
不是所有对象都适合保留原型:比如
Promise、Function、Error的实例有内部状态或不可代理行为,强行拷贝原型可能无效甚至报错。 -
自定义类需确保构造函数无副作用:若依赖
new初始化,仅靠Object.create()+defineProperties不会调用构造函数,状态可能不完整。 -
避免污染原对象原型:不要在原型上添加可枚举方法,否则会被
for...in遍历到并拷入新对象——应使用Object.defineProperty设为enumerable: false。 -
现代替代方案:若目标环境支持,可考虑用
structuredClone+ 后续手动修复原型(适用于简单场景),或使用成熟库如lodash.cloneDeep(它不保留原型,但提供customizer钩子可干预)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










