JavaScript深克隆实现方法 使用Object.create复制对象原型链
如何利用 Object.create(Object.getPrototypeOf(obj)) 实现具备相同原型结构的深克隆

开门见山地说,Object.create(Object.getPrototypeOf(obj)) 这行代码,常被误认为是实现深克隆的捷径。但真相是,它仅仅创建了一个**继承原对象原型链的空壳**,既不复制任何属性,更谈不上深克隆。把它当作“深克隆方法”,是一个流传甚广的误解。
它实际做了什么
让我们拆解一下这行代码的执行过程,它其实只干了两件事:
- 第一步,获取 obj 的原型对象(也就是
obj.__proto__或Object.getPrototypeOf(obj)指向的那个对象)。 - 第二步,用这个原型对象作为新对象的 [[Prototype]],创建一个**全新的、空荡荡的对象**——注意,这个新对象没有任何自有属性。
结果就是:新对象和原对象在原型链上确实是“亲戚”,共享同一套继承体系。但原对象身上那些实实在在的数据属性(比如 {a: 1, b: {c: 2}} 里的 a 和 b),一个都没被复制过去。新对象只是个空架子。
为什么它不能实现深克隆
那么,真正的深克隆到底要求什么?标准可不低:
- 需要递归地复制所有自有属性,无论是可枚举的还是不可枚举的,甚至包括 Symbol 类型的属性。
- 对嵌套的对象、数组,以及 Date、RegExp 这些内置类型,要有类型识别能力,进行针对性的拷贝。
- 保持原型关系(既然你提到了“相同原型结构”,那这一点就是必须项)。
- 妥善处理循环引用,否则递归过程很容易栈溢出。
回头再看 Object.create(...),它连最基础的属性复制这一步都没迈出去,后面的所有高级要求自然也就无从谈起了。
如何真正实现“保留原型的深克隆”
想达到目的,思路必须清晰:分两步走。先深拷贝所有属性值,再为拷贝结果设置正确的原型。一个比较稳妥的组合方案是:
- 属性拷贝:使用
Object.getOwnPropertyDescriptors(obj)获取对象所有的属性描述符。这个方法很强大,能拿到包括 getter/setter、可写性(writable)、可枚举性(enumerable)、可配置性(configurable)在内的完整信息。然后,用Object.defineProperties(新对象, descriptors)将这些属性定义到新对象上。 - 原型设置:有两种方式。一是用
Object.setPrototypeOf(新对象, Object.getPrototypeOf(obj))事后设置;二是在用Object.create创建空对象时就传入原型(但切记,此时属性还是空的,需要后续补充)。 - 递归处理值:这才是深克隆的核心。对每个属性值进行类型判断——普通对象或数组就递归克隆;Date、RegExp、Map、Set 等需要调用对应的构造函数重新生成;基本类型直接返回;null 和 undefined 无需处理;最关键的是,要设计一个缓存机制来应对循环引用,避免无限递归。
下面是一个简化的示意代码(暂未处理循环引用):
function cloneWithPrototype(obj) {
if (obj === null || typeof obj !== 'object') return obj;
// 创建空对象,并将其原型设为 obj 的原型
const cloned = Object.create(Object.getPrototypeOf(obj));
// 获取原对象所有自有属性的描述符
const descriptors = Object.getOwnPropertyDescriptors(obj);
// 逐个克隆属性值并定义到新对象
for (const key in descriptors) {
const desc = descriptors[key];
if ('value' in desc) {
desc.value = cloneWithPrototype(desc.value); // 递归克隆值
}
Object.defineProperty(cloned, key, desc);
}
return cloned;
}
更实用的建议
- 在日常开发中,除非有特殊原因,否则优先考虑使用成熟的工具库,比如
lodash.cloneDeep。从 4.17+ 版本开始,它默认就会保留原型,并且已经健壮地处理了各种边界情况,省心又可靠。 - 如果确实需要手写实现,思路一定要清晰:将“原型继承”和“属性深拷贝”视为两个独立的职责,分别处理,不要试图用
Object.create一步到位。 - 最后,必须清醒地认识到,有些东西是无法真正深克隆的,比如函数、DOM 节点、Error 对象实例等。对于这些,通常只能进行浅拷贝引用,或者设计特殊的处理逻辑。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
HTML文档版权声明结构与基础脚手架代码质量解析
版权声明需用语义化``标签,©符号使用`©`实体;年份通过服务端、构建或JS分层兜底;`rel= "license "`仅指向真实许可证文件才有效。
基于HTML模板的组件化全栈交互方案
纯HTML模板仅作静态结构,全栈交互需后端注入数据、前端JS激活模板及用户事件触发双向链路。``标签惰性,不执行脚本或加载资源,需通过克隆填充数据并手动绑定事件实现交互,父子传值依赖JS对象与自定义事件。
Bootstrap修改Badge徽章边框为圆点样式的方法
将BootstrapBadge改为圆点:自定义类设置等宽高、border-radius:50%、padding:0、font-size:0;Bootstrap5 2+需加display:inline-block;注意垂直对齐,避免min-width干扰,背景色用background-color更可控。
Bootstrap响应式多栏Footer社交图标排版实现
使用Bootstrap5的grid系统实现响应式多栏Footer,按断点设置列宽;以FontAwesome处理社交图标,避免使用img;利用list-unstyled组织链接列表;借助flex-column容器配合mt-auto使版权行自动贴底,避免fixed-bottom固定定位的弊端。
如何用Shadow DOM隔离组件内外事件分发
ShadowDOM内部事件默认不穿透影子边界,需显式设置composed:true才能被外部监听。此时event target指向宿主元素,真实触发源需通过event composedPath()[0]获取。原生事件手动派发时也必须声明composed:true。应避免在宿主上依赖event target做逻辑分支,建议在shadowroot内绑定事件或使用
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-07 07:02
2026-07-07 07:02
2026-07-07 07:02
2026-07-07 07:02
2026-07-07 07:02
2026-07-07 07:02
2026-07-07 07:01
2026-07-07 07:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

