游乐游手机版
首页/前端开发/文章详情

JavaScript深克隆实现方法 使用Object.create复制对象原型链

时间:2026-05-06 16:29
Object create(Object getPrototypeOf(obj))仅创建继承原对象原型的空对象,不复制任何属性,无法实现深克隆。真正的深克隆需递归复制所有自有属性、处理嵌套对象与内置类型、保持原型并应对循环引用。建议分步处理:先深拷贝属性,再设置正确原型,或使用成熟工具库如lodash cloneDeep。

如何利用 Object.create(Object.getPrototypeOf(obj)) 实现具备相同原型结构的深克隆

如何利用 Object.create(Object.getPrototypeOf(obj)) 实现具备相同原型结构的深克隆

开门见山地说,Object.create(Object.getPrototypeOf(obj)) 这行代码,常被误认为是实现深克隆的捷径。但真相是,它仅仅创建了一个**继承原对象原型链的空壳**,既不复制任何属性,更谈不上深克隆。把它当作“深克隆方法”,是一个流传甚广的误解。

它实际做了什么

让我们拆解一下这行代码的执行过程,它其实只干了两件事:

  • 第一步,获取 obj 的原型对象(也就是 obj.__proto__Object.getPrototypeOf(obj) 指向的那个对象)。
  • 第二步,用这个原型对象作为新对象的 [[Prototype]],创建一个**全新的、空荡荡的对象**——注意,这个新对象没有任何自有属性。

结果就是:新对象和原对象在原型链上确实是“亲戚”,共享同一套继承体系。但原对象身上那些实实在在的数据属性(比如 {a: 1, b: {c: 2}} 里的 ab),一个都没被复制过去。新对象只是个空架子。

为什么它不能实现深克隆

那么,真正的深克隆到底要求什么?标准可不低:

  • 需要递归地复制所有自有属性,无论是可枚举的还是不可枚举的,甚至包括 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 对象实例等。对于这些,通常只能进行浅拷贝引用,或者设计特殊的处理逻辑。
来源:https://www.php.cn/faq/2426544.html
上一篇HTML5 LocalStorage如何保存视频清晰度偏好设置 下一篇HTML5 Canvas绘制雷达扫描图教程与交互实现
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
如何在JavaScript中实现基于旋转视野的FOV射线绘制详解
前端开发 · 2026-07-01

如何在JavaScript中实现基于旋转视野的FOV射线绘制详解

如果用一句话概括核心,那就是:在 RayCasting 游戏开发中,绘制动态视野边界线(FOV)最可靠的方式是在逻辑层通过数学公式将坐标“算”出来,而不是依赖 Canvas 绘图上下文的旋转操作。 在实现类似 Doom 风格的 RayCasting 游戏时,动态视野(Field of View, F

TypeScript后端数据正确映射为前端接口类型的方法
前端开发 · 2026-07-01

TypeScript后端数据正确映射为前端接口类型的方法

在后端数据与前端类型之间来回转换,几乎是每位 TypeScript 开发者都无法回避的常态。后端返回的 car_brand、reg_number,和前端接口中定义的 brand、govtNumber,命名风格常常对不上号。此时,如果为了省事直接用 as 类型断言“强行”指认类型,那就踩进了常见的陷阱

动态HTML表格按层级条件合并单元格的JavaScript实现
前端开发 · 2026-07-01

动态HTML表格按层级条件合并单元格的JavaScript实现

本文详细讲解一种递归式 JavaScript 合并单元格方法,用于按列优先级(如前3列)智能合并表格行:仅当前一列已合并的前提下,才允许后续列合并相同值,从而精准实现多级分组与层级表格合并效果。 在动态生成的 HTML 表格中,按业务逻辑合并重复行是常见需求。然而,简单地对单列分别遍历合并——例如先

Next.js 13+重定向后滚动失效解决方案
前端开发 · 2026-07-01

Next.js 13+重定向后滚动失效解决方案

在 Next js App Router 的日常开发中,有一个令人颇为困扰的异常现象——当服务端执行 `redirect()` 跳转后,目标页面竟然无法正常滚动。没错,页面已经渲染完成,内容也完整显示,但垂直滚动条仿佛凭空消失。这个问题在 Next js 13 5 4 版本中尤为突出。 先给出结论:

WebGL图像加载延迟的纹理初始化时立即显示方法
前端开发 · 2026-07-01

WebGL图像加载延迟的纹理初始化时立即显示方法

本文详细介绍如何利用 Promise 与 async await 重构 WebGL 纹理加载流程,彻底解决首次渲染显示蓝色占位色、需要手动交互才能刷新的问题,实现文件导入后四张纹理平面即时正确渲染。 实际上,这个坑在 WebGL 开发中相当常见——纹理异步加载的小陷阱,说起来不大,但第一次遇到确实令