【手撕代码系列】JS手写实现深拷贝

2023-11-09

在这里插入图片描述

公众号:Code程序人生,分享前端所见所闻

深拷贝是在计算机科学中非常重要的概念,尤其是在处理数据结构和对象的时候。深拷贝的目的是创建一个新的对象,它有自己的内存空间,并且其中的所有值都是原始对象的副本。这样做的好处是,可以避免在修改新对象时,对原始对象产生影响。

JavaScript 中,我们可以使用 JSON.parse()JSON.stringify() 方法来进行深拷贝,但是这种方法有一些限制,比如无法处理函数和循环引用等情况。因此,在这篇博客中,我们将手写实现深拷贝。

实现方法

首先,我们需要定义一个 deepClone 函数来进行深拷贝。这个函数接受一个对象作为参数,返回一个新的对象。在函数内部,我们需要对对象进行递归遍历,直到所有的值都被拷贝完成。
下面是一个简单的实现方法:

function deepClone(obj) {
  let clone = {};

  for (let key in obj) {
    if (typeof obj[key] === "object" && obj[key] !== null) {
      clone[key] = deepClone(obj[key]);
    } else {
      clone[key] = obj[key];
    }
  }

  return clone;
}

这个方法的实现比较简单,但是它无法处理函数和循环引用。接下来,我们将分别介绍如何处理这两种情况。

处理函数

要处理函数,我们需要在拷贝对象时,判断当前值是否为函数。如果是函数,我们需要使用Function.prototype.toString() 方法来获取函数的源代码,并使用 new Function() 来创建一个新的函数。

下面是一个实现方法:

function deepClone(obj) {
  let clone = {};

  for (let key in obj) {
    if (typeof obj[key] === "function") {
      clone[key] = new Function("return " + obj[key].toString())();
    } else if (typeof obj[key] === "object" && obj[key] !== null) {
      clone[key] = deepClone(obj[key]);
    } else {
      clone[key] = obj[key];
    }
  }

  return clone;
}

处理循环引用

循环引用是指一个对象中包含对自身的引用。处理循环引用是比较复杂的,需要使用一些技巧来判断是否已经拷贝过该对象。

一种实现方法是,使用一个 Map 对象来记录已经拷贝过的对象。每次遍历一个对象时,我们首先检查它是否已经被拷贝过。如果是,我们直接返回该对象的引用;如果不是,我们先将该对象添加到 Map 中,并递归地拷贝它的属性。

下面是一个实现方法:

function deepClone(obj, map = new Map()) {
  if (map.has(obj)) {
  	return map.get(obj);
  }
  
  let clone = {};
  
  for (let key in obj) {
  	if (typeof obj[key] === "function") {
  		clone[key] = new Function("return " + obj[key].toString())();
    } else if (typeof obj[key] === "object" && obj[key] !== null) {
      map.set(obj, clone);
      clone[key] = deepClone(obj[key], map);
    } else {
   	 	clone[key] = obj[key];
    }
	}
  
  return clone;
}

在这个方法中,我们添加了一个额外的参数 map,用来记录已经拷贝过的对象。如果该对象已经被拷贝过,我们直接返回该对象在 Map 中的引用。如果没有被拷贝过,我们将该对象添加到 Map 中,并递归地拷贝它的属性。

测试代码

为了测试我们实现的 deepClone 函数,我们可以创建一个包含不同类型的值的对象,并使用该函数进行拷贝。然后,我们可以修改拷贝后的对象,并检查原始对象是否发生了改变。 下面是一个测试代码示例:

下面是一个测试代码示例:

let originalObj = {
  a: 1,
  b: "hello",
  c: [1, 2, 3],
  d: { e: "world" },
  f: function () {
    console.log("hello world");
  },
};

let clonedObj = deepClone(originalObj);

clonedObj.a = 2;
clonedObj.b = "world";
clonedObj.c[0] = 4;
clonedObj.d.e = "world";

console.log(originalObj);
console.log(clonedObj);

输出结果如下:

{ a: 1, b: 'hello', c: [ 1, 2, 3 ], d: { e: 'world' }, f: [Function: f] }
{ a: 2, b: 'world', c: [ 4, 2, 3 ], d: { e: 'world' }, f: [Function: f] }

从输出结果可以看出,我们成功地拷贝了原始对象,并且在修改拷贝后的对象时,并没有影响到原始对象。

总结

在本篇博客中,我们介绍了如何手写实现深拷贝。我们实现了一个递归遍历对象的函数,同时处理了函数和循环引用的情况。这个方法虽然比较简单,但是它可以很好地处理大部分情况,避免在修改对象时对原始对象产生影响。

需要注意的是,手写实现深拷贝可能会带来一些性能上的问题。因为递归地遍历对象需要消耗大量的时间和内存。在处理大型对象或者嵌套层次很深的对象时,可能会出现性能问题。因此,建议在实际开发中,使用成熟的深拷贝库,如 lodashcloneDeep 方法,可以更好地处理性能问题。

另外,在手写实现深拷贝时,需要特别注意循环引用的问题。循环引用可能导致死循环,使程序崩溃。因此,我们需要使用一个 Map 来记录已经拷贝过的对象,以避免出现循环引用的问题。

总的来说,手写实现深拷贝是一个非常有用的技能。在实际开发中,我们经常需要处理对象的拷贝问题,特别是当我们需要对一个对象进行修改,但是又不想对原始对象造成影响时,深拷贝就显得非常有用了。

最后,值得一提的是,深拷贝并不是万能的解决方案。在某些情况下,浅拷贝甚至是更好的选择。因此,在实际开发中,我们需要根据具体情况选择合适的拷贝方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【手撕代码系列】JS手写实现深拷贝 的相关文章

  • 在 Three.js 中绕点旋转对象的正确方法是什么?

    关于 Three js 的大多数教程 问题都建议使用 Three js 绕点旋转对象的方法是在要旋转的位置创建父对象 附加对象 然后移动子对象 然后 当父级旋转时 子级围绕该点旋转 例如 Make a pivot var pivot new
  • jQuery mobile 中的文本区域高度和宽度?

    我修复了 jQuery mobile 中文本区域元素的高度 并且在纵向中得到了完美的高度和宽度 但在横向中宽度没有放大 谁能帮我 提前致谢 HTML
  • Angular UI 模式的范围问题

    我无法理解 使用角度 UI 模式的范围 虽然这里不是很明显 但我已经正确设置了模块和所有内容 据我所知 但这些代码示例尤其是我发现错误的地方 index html 其中重要部分 div class btn group div
  • 在 HTML5 Javascript 中将 BlobBuilder 转换为字符串

    function blobToString blob var reader new FileReader var d reader onloadend function d callback reader result console lo
  • 如何删除除任何特定 id 之外的元素

    假设有一个父 id 其中包含许多元素 我想删除除一个元素之外的所有元素 ex parent id children not id n remove
  • 使用本地存储在从另一个表保存的 HTML TABLE 中打印 JSON,以便我在另一个页面上打印我的表

    在我的作业中 我必须使用用户输入中的数据并将数据保存在本地存储中 我必须以水平表格式将这些数据从本地存储打印到其他页面 为此 我编写了用于用户输入并将数据保存在本地存储中的代码 div p p div
  • 在 Internet Explorer 中使用什么来监视 jscript 内存使用情况

    我们正在调试 GWT 应用程序 在 Firefox 中运行正常 在 IE6 0 中开始运行正常 但一段时间后 它就会崩溃并开始爬行 经过一些测试后 我们怀疑存在一些内存问题 使用了太多内存 内存泄漏等 除了使用taskmanager和pro
  • 位置特征检测:固定

    我正在尝试找到一个脚本来检测设备是否放置position fixed元素相对于视口而不是整个文档 目前 标准桌面浏览器和 Mobile Safari 适用于 iOS 5 都是这样做的 而 Android 设备则相对于整个文档放置固定元素 我
  • 想要动态处理与分页相关的页码显示:ReactJS

    我有一些分页逻辑工作得很好 唯一的问题是我只能让它显示并固定数量的页面可供选择 现在我已经把它放到了 5 页 但我希望它能够根据总记录动态更改 假设我有 100 条记录 每页限制为 10 条 将有 10 页 现在我只能让它以这种方式显示 第
  • ReactTransitionGroup 不适用于 React-redux 连接组件

    我正在开发一个更大的项目 但我创建了这个简短的示例来说明问题 如果我使用Box组件 它的工作原理 它在控制台中输出componentWillEnter and componentWillLeave当我们点击按钮时 如果我使用BoxConta
  • 如何使用 Greasemonkey 监视静态 HTML 页面的更改?使用哈希?

    我希望我的 Greasemonkey 脚本仅在其访问的静态页面具有与以前完全相同的内容时运行 现在我可以设置一个包含该页面哈希的变量 我正在寻找一种动态散列页面的方法 以便我可以将我的散列与生成的散列进行比较 关于如何即时实现散列的任何想法
  • React autoFocus 将光标设置为输入值的开头

    我有一个受控输入 最初显示一个值 我已将该输入设置为自动聚焦 但当我希望它出现在末尾时 光标出现在输入的开头 我知道这可能是因为自动对焦是在值之前添加的 但我不能 100 确定 在输入字段末尾完成光标初始化的最佳方法是什么 var Test
  • React Router v4 不渲染组件

    React Router v4 渲染组件存在问题 在应用程序初始加载时 它将呈现与 URL 相对应的正确组件 但是 任何后续的组件Link单击不会呈现所需的组件 图书馆 反应路由器 4 2 2 https reacttraining com
  • mongodb 聚合 - 累积字段的不同组值

    如果我有Player表格文件 name String score Int 我有Group文档 其中组代表玩家列表 groupName String players ObjectID 玩家可以属于多个组 我想做一个聚合Player文档 按以下
  • 使用 JavaScript 移动页面上的按钮

    我的按钮可以移动 但奇怪的是 我无法弄清楚偏移是否有问题 我希望我的按钮随着鼠标光标移动 但现在它的移动方式不是我想要的 有时它会消失 另外 创建的新按钮是重叠的 我不知道如何解决这个问题并拥有更好的外观 var coorA var coo
  • 如何始终将焦点保持在文本框中

    我创建了一个包含两个 div 的 HTML 页面 左侧的 div 页面的 90 是 ajax 结果的目标 右侧的 div 页面的 10 包含一个文本框 该页面的想法是在文本框中输入零件编号 通过条形码扫描仪 并显示与该零件编号匹配的绘图 显
  • 如何正确取消引用然后删除 JavaScript 对象?

    我想知道从内存中完全取消引用 JavaScript 对象的正确方法 确保删除时不会在内存中悬空 并且垃圾收集器会删除该对象 当我看这个问题时在 JavaScript 中删除对象 https stackoverflow com questio
  • 如何计算特定字符在字符串中出现的次数

    我正在尝试创建一个函数来查看数组中的任何字符是否在字符串中 如果是 有多少个 我尝试计算每一种模式 但是太多了 我尝试使用 Python 中的 in 运算符的替代方案 但效果不佳 function calc fit element var
  • 使用 Google 日历源时如何禁用 FullCalendar 中的活动链接?

    我正在使用 FullCalendar 库从 Google 日历加载日历中的事件 不幸的是 事件添加到日历后 它们是可点击的 当您点击该活动时 您会自动重定向到 Google 日历页面以查看该特定活动 或者如果您有足够的访问权限 则可以直接对
  • 使用 Javascript 设置 cookie [重复]

    这个问题在这里已经有答案了 我正在尝试构建我的第一个移动应用程序 它需要连接到我的 mysql 数据库并使用 json 返回数据 这很好 目前我有一个登录系统 一旦确定用户名和密码存在 它就会返回一条成功消息 对于下一步 我想在我的页面上使

随机推荐

  • Postman工具——环境变量与全局变量

    转载请注明出处 http blog csdn net water 0815 article details 53326990 本文同步发表于我的微信公众号和简书社区 微信公众号 惜福 xifu forever 扫一扫文章底部的二维码即可关注
  • 使用popen实现system函数功能

    之前写Linux应用程序的时候 最喜欢使用system命令了 后来发现这个命令使用需要很谨慎 之前使用该命令来进行MD5校验 通过返回值来判断校验是否成功不够严谨 有时候因为system调用MD5sum文件不存在导致的错误 应用并不能够直观
  • LeetCode 200. 岛屿数量(C++)

    题目地址 力扣 思路 我们从题目中可以得知 孤立的岛屿旁边全是海 假设岛屿范围内每一个 1 是岛屿的一部分 那么我们从岛屿的任意一部分登岛 走遍岛屿的每一块土地 就能确定岛屿的大小和范围 为了找遍所有的岛屿 我们还需要把走过的土地都标记为0
  • 音频常见问题总结

    音频文件在播放时出现断断续续或类似 爆破 Pop Click 杂音的现象 称之为 Xrun 可以是 underrun 也可以是 overrun 原因 通常来说 出现 Xrun 问题时原因可能是以下几个之一 1 Linux CFS 调度器导致
  • el-select下拉框多选远程搜索反显

  • 若依框架前端打包踩坑

    官网 http www ruoyi vip 目录 一 下载并运行项目 二 关于 若依 接口地址配置 2 1 若依的跨域代理介绍 2 2 配置跨域代理 调用后台接口 2 2 1 配置 后台 ip 地址 2 2 2 页面报 系统接口404 错误
  • 创宇技能表_[OPEN]知道创宇研发技能表

    HI 今天夜里 我们开放知道创宇的研发技能表 这个Checklist里大体总结了在知道创宇做研发工作所需具备的技能 包括我们的安全研究员 这份技能表给出了许多的 点 而 面 需要靠自己 否则如何证明自己具备牛逼的学习能力 技能都样样掌握好的
  • base64图片互转

    String a base substring base indexOf 1 GenerateImage a D 1 jpg public static void main String args throws IOException St
  • 使用Nginx发布Vue应用

    使用Nginx发布Vue应用 本教程将指导你如何使用Nginx来发布Vue应用 Nginx是一个高性能的开源Web服务器 可以用于部署静态网页 反向代理和负载均衡等 Vue是一个流行的JavaScript框架 用于构建现代化的单页面应用 步
  • [人工智能-综述-1]:人工智能系统架构

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 人工智能 综述 1 人工智能系统架构 文火冰糖 王文兵 的博客 CSDN博客 人工智能系统的技术架构 目录 第1部分 什么是人工智能 1 1
  • VLC Activex控件(VideoLAN.VLCPlugin.1 VideoLAN.VLCPlugin.2)的所有方法 属性及使用 在一个老外的网站上找到的...

    无意中在老外的网站上发现了关于VideoLAN VLCPlugin 1和VideoLAN VLCPlugin 2所有方法和属性 公布出来 以免大家浪费时间去寻找 注意你使用的VLC的版本 If you open a link to a vi
  • 构建配置ESP8266 MQTT服务器

    我们将了解ESP8266模块如何通过消息队列遥测传输 MQTT 相互通信 MQTT术语 已经使用了诸如中央代理 主题 发布 订阅之类的术语 因此现在该用类似于邮局的方式来解释它们了 消息是报纸或杂志 代理 这是一个接收客户端消息的软件应用程
  • 数字保留有效数字_分析化学有效数字问题

    一 有效数字位数 1 非整数数据 有效数字为从左端开始首个非零数字算起 至右端最后一个数字 例如 0 0518有3位有效数字 0 05180有4位有效数字 同时注意 在用科学记数法表示数字时 有效数字位数不变 例如 0 05180改写为 2
  • SpringMVC+Freemarker+jQuery实现多语言(国际化)切换

    一 spring启动配置文件修改 其中
  • cuda与driver对应版本

    CUDA Toolkit Toolkit Driver Version Linux x86 64 Driver Version Windows x86 64 Driver Version CUDA 11 4 Update 1 gt 470
  • 数据结构-带头节点的单链表(C语言)超详细讲解

    前面我们学到线性表的顺序存储结构 顺序表 发现它有着明显的缺点 插入和删除元素时需要频繁的移动元素 运算效率低 必须按事先估计的最大元素个数申请连续的存储空间 存储空间估计大了 造成浪费空间 估计小了 容易产生溢出 空间难以扩大 采用链式存
  • 每天Leetcode 刷题 初级算法篇-杨辉三角

    题目要求 力扣题解 代码 import java util ArrayList import java util List program mydemo description this is a class author Mr zeng
  • GraphEdit 的作用和使用

    GraphEdit 是一个用于建立和测试Filter graph 的可视化工具 在DirectX SDK 中附带一个可执行程序版本 在SDK目录中的Bin DXUtils graphedt exe 你可以快速测试你建立的Filter gra
  • ORACLE存储层次以及彻底删除数据的方式

    ORACLE的存储层测氛围四层 表空间 就是实际的数据文件 对应操作系统上的一个数据文件 段 回滚段 数据段 索引段 区 ORACLE分配磁盘空间的最小单元 块 ORACLE操作数据库的最小IO单元 user tables user ind
  • 【手撕代码系列】JS手写实现深拷贝

    公众号 Code程序人生 分享前端所见所闻 深拷贝是在计算机科学中非常重要的概念 尤其是在处理数据结构和对象的时候 深拷贝的目的是创建一个新的对象 它有自己的内存空间 并且其中的所有值都是原始对象的副本 这样做的好处是 可以避免在修改新对象