js关于深度克隆问题

news2024/9/19 19:58:09

js的克隆是一个老生常谈的内容了,今天没啥好写的,就写这个了

要搞清楚js的克隆,就需要先搞清楚js中的数据类型,js中数据类型分为两大类

类型说明
原始类型-
string字符串类型,用于表示文本数据。
number数字类型,包括整数和浮点数,用于表示数值数据。
boolean布尔类型,true 或 false,用于表示逻辑值。
null空值,表示无或不存在任何值。
undefined未定义值,表示变量未被赋值或初始化。
bigint大整数类型,可以表示任意大的整数(使用n作为后缀,如 100n)。
symbol符号类型,用于创建唯一的标识符(使用Symbol()创建)。
引用类型-
object对象类型,由一组键值对组成,用于表示复杂的数据结构(使用大括号 {} 创建)。

原始类型的拷贝确实相对简单,因为这些类型通常存储在栈上,这意味着它们在内存中占据的空间是固定的,并且可以直接通过指针进行复制。
在这里插入图片描述

tips:如果你还搞不清楚什么是引用类型,什么是原始类型,可以看我的这篇文章
javascript数据类型与引用类型的区别以及原始值详解

然而,对于引用类型来说,拷贝则要复杂得多。引用类型的对象(例如数组或对象)存储在堆上,这意味着它们在内存中占据的空间是不固定的,并且由垃圾回收器管理。因此,对于引用类型的拷贝,我们不能简单地复制指针,而是需要复制整个对象。

为了实现引用类型的拷贝,我们需要使用深拷贝(deep copy)或浅拷贝(shallow copy)的技术。深拷贝会复制对象的所有嵌套对象和数组,而浅拷贝则只会复制对象的顶层结构。

需要注意的是,深拷贝可能会导致性能问题,因为它需要复制大量的数据。因此,对于大型的引用类型对象,我们可能需要使用一些优化技巧来避免深拷贝,例如使用对象代理(object proxy)或冻结(freeze)对象来阻止修改。

对于简单对象的拷贝_数组克隆

数组对象作为引用类型,栈内存储的是这个数组对象的堆内引用地址,因为对象类型通常比较庞大,这是数据开销和内存开销优化的手段,也就是说,对于数组只通过简单=赋值符号赋值的话,是行不通的
示例如下

	var x = [1,2,3];
	var y = x;
	console.log(y);  //[1,2,3]
	y.push(4);
	console.log(y);  //[1,2,3,4]
	console.log(x);  //[1,2,3,4]

对于这种情况,我们可以通过一个循环,简单粗暴的解决这个问题

	var x = [1, 2, 3];
	var y = [];
	x.forEach(v => y.push(v))
	console.log(y);  //[1,2,3]
	y.push(4);
	console.log(y);  //[1,2,3,4]
	console.log(x);  //[1,2,3]

对于简单对象的拷贝_简单对象的克隆

	var x = {a:1,b:2};
	var y = {};
	for(var i in x){
	    y[i] = x[i];
	}
	console.log(y);  //Object {a: 1, b: 2}
	y.c = 3;
	console.log(y);  //Object {a: 1, b: 2, c: 3}
	console.log(x);  //Object {a: 1, b: 2}

当然,我知道还有一种更加简单高效的方法,那就是使用json的序列化.但是这里先不讲它,后面再讲

对于简单对象的拷贝_函数的克隆

由于函数对象克隆之后的对象会单独复制一次并存储实际数据,因此并不会影响克隆之前的对象。所以采用简单的复制“=”即可完成克隆。

	var x = function(){console.log(1);};
	var y = x;
	y = function(){console.log(2);};
	x();  //1
	y();  //2

JavaScript浅克隆和深度克隆

浅克隆(Shallow Clone)和深度克隆(Deep Clone)是 JavaScript 中用来复制对象或数组的两种主要方法。它们的主要区别在于复制过程中对嵌套对象或数组的处理方式。

  1. 浅克隆:
    浅克隆只复制对象或数组的顶层元素,对于嵌套的对象或数组,它只复制了引用,而没有复制内部的元素。这意味着,如果你改变了复制的对象或数组,原对象或数组也会被改变,因为它们指向的是同一份数据。
  2. 深度克隆:
    深度克隆会复制对象或数组的所有层级的元素。这意味着,对于嵌套的对象或数组,它会创建完全独立的副本。这样,改变复制的对象或数组不会影响到原对象或数组。在 JavaScript 中,可以使用 JSON.parse(JSON.stringify(obj)) 方法来进行深度克隆。但是这个方法只能用于对象或数组不包含函数、RegExp、Date、Infinity、NaN、undefined、Infinity、NaN的情况

浅克隆示例如下

	// 浅克隆函数
	function shallowClone(obj) {
	  let clone = Array.isArray(obj) ? [] : {}
	  for (let key in obj) {
	    if (obj.hasOwnProperty(key)) {
	      clone[key] = obj[key]
	    }
	  }
	  return clone;
	}
	// 被克隆对象
	const oldObj = {
	  a: 1,
	  b: ['1', '2', '3'],
	  c: { d: { e: 2 } }
	};
	
	const newObj = shallowClone(oldObj);
	console.log(newObj.c.d, oldObj.c.d); // { e: 2 } { e: 2 }
	console.log(oldObj.c.h === newObj.c.h); // true
	newObj.c.d = 100 //改变newObj
	console.log(oldObj.c.d) //oldObj随之改变

我们可以很明显地看到,虽然oldObj.c.d被克隆了,但是它还与oldObj.c.d相等,这表明他们依然指向同一段堆内存,我们上面讨论过了引用类型的特点,这就造成了如果对newObj.c.d进行修改,也会影响oldObj.c.d。这往往不是我们想要的

所以我们需要构建一个深度克隆函数

深度克隆

上面我们讲过,使用json的序列化和反序列化可以对简单对象进行深度拷贝,而当对象中出现诸如function 、RegExp、Date、Infinity、NaN、undefined 或 Symbol 等等之类的类型时,json的序列化便处理不了,而且有循环引用的时候更是会直接报错

但反过来想,如果我们针对这些特殊情况做处理,那不就能实现深度克隆了吗

所以我们先要获取不同对象的类型做出判断,这样我们就可以对特殊对象进行类型判断了,从而采用针对性的克隆策略.

	const isType = (obj, type) => {
	  if (typeof obj !== 'object') return false
	  // 判断数据类型的经典方法:
	  const typeString = Object.prototype.toString.call(obj)
	  let flag
	  switch (type) {
	    case 'Array':
	      flag = typeString === '[object Array]'
	      break
	    case 'Date':
	      flag = typeString === '[object Date]'
	      break
	    case 'RegExp':
	      flag = typeString === '[object RegExp]'
	      break
	    default:
	      flag = false
	  }
	  return flag
	};

测试一下

	const arr = Array.of(3, 4, 5, 2)
	console.log(isType(arr, 'Array'))

类型识别正常
在这里插入图片描述

对于正则对象,我们在处理之前要先补充一点新知识.
我们需要通过正则的扩展了解到flags属性等等,因此我们需要实现一个提取flags的函数

	const getRegExp = re => {
	  var flags = ''
	  if (re.global) flags += 'g'
	  if (re.ignoreCase) flags += 'i'
	  if (re.multiline) flags += 'm'
	  return flags
	}

昨晚前置工作,就是把这些方法组合起来了,而且为了防止有循环引用,我们这里使用递归来进行遍历属性

	const clone = parent => {
	  const parents = [];
	  const children = [];
	  const _clone = parent => {
	    if (parent === null) return null;
	    if (typeof parent !== 'object') return parent;
	    let child, proto;
	    if (isType(parent, 'Array')) {
	      child = [];
	    } else if (isType(parent, 'RegExp')) {
	      child = new RegExp(parent.source, getRegExp(parent));
	      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
	    } else if (isType(parent, 'Date')) {
	      child = new Date(parent.getTime());
	    } else {
	      proto = Object.getPrototypeOf(parent);
	      child = Object.create(proto);
	    }
	    const index = parents.indexOf(parent);
	
	    if (index != -1) {
	      return children[index];
	    }
	    parents.push(parent);
	    children.push(child);
	
	    for (let i in parent) {
	      child[i] = _clone(parent[i]);
	    }
	
	    return child;
	  };
	  return _clone(parent);
	};

声明一个复杂一点的对象来做测试

	class person {
	  constructor(pname) {
	    this.name = pname
	  }
	}
	
	const Messi = new person('Messi');
	
	function say() {
	  console.log('hi');
	}
	const oldObj = {
	  a: say,
	  c: new RegExp('ab+c', 'i'),
	  d: Messi,
	};
	oldObj.b = oldObj;
	const newObj = deepClone(oldObj);
	
	console.log(newObj)
	console.log(newObj==oldObj)

如下,
在这里插入图片描述

对于一些对象属性只是原始类型或数组的对象但又有循环嵌套的对象处理方法

如标题所示,其实很多时候,要拷贝的对象没有那么复杂,所以我们可以使用简单一点的方法来实现深拷贝

对于对象属性只是原始类型或数组的对象但又有循环嵌套的对象处理方法如下
方法一

	function deepClone(obj) {
	  const objectMap = new Map()
	  const _deepClone = value => {
	    const type = typeof value
	    if (type !== 'object' || type === null) return value
	    if (objectMap.has(value)) return objectMap.get(value)
	    const result = Array.isArray(value) ? [] : {}
	    objectMap.set(value, result)
	    for (const key in value) {
	      result[key] = _deepClone(value[key])
	    }
	    return result
	  }
	  return _deepClone(obj)
	}

声明一个简单的对象来测试一下

在这里插入图片描述
方式二

	function deepClone(obj) {
	  return new Promise(res => {
	    const { port1, port2 } = new MessageChannel()
	    port1.postMessage(obj)
	    port2.onmessage = msg => {
	      res(msg.data)
	    }
	  })
	}

继续拿刚刚那个对象做测试

	let oldObj = {
	  a: 11,
	  b: '123',
	  c: [1, 2, 3, '4']
	}
	oldObj.d = oldObj
	deepClone(oldObj).then(v => {
	  console.log(v)
	  console.log(newObj == oldObj)
	})

输出如下
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1126116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

django 商品及购物车逻辑实现

基于类视图模式实现商品分类菜单接口开发 创建菜单子应用 python manage.py startapp menu测试 apps/menu/views from django.http import HttpResponse from django.views import Viewclass GoodsMainMenu(View):def get(self,request):print("get请求")return …

数据类型【MySQL】

文章目录 数据类型分类 数值类型TINYINTINTBITFLOATDECIMAL 字符串类型CHARVARCHARCHAR 和 VARCHAR 的比较BLOBTEXTNULL 和 日期和时间类型ENUM 和 SETFIND_IN_SET 参考资料 阅读前导:SQL 规定关键字应该大写,实际上在命令行使用 SQL 语句时为了方便和可…

Spring | Spring Cache 缓存框架

Spring Cache 缓存框架: Spring Cache功能介绍Spring Cache的Maven依赖Spring Cache的常用注解EnableCaching注解CachePut注解Cacheable注解CacheEvict注解 Spring Cache功能介绍 Spring Cache是Spring的一个框架,实现了基于注解的缓存功能。只需简单加一…

40 JAVA安全-JWT安全及预编译CASE注入等

目录 SQL Injection(mitigation)演示案例:Javaweb-SQL注入攻击-预编译机制绕过Javaweb-身份验证攻击-JWT修改伪造攻击 jwt加解密:https://jwt.io/#debugger-io 通过前期的WEB漏洞的学习,掌握了大部分的安全漏洞的原理及利用,但在各种脚本语言…

互联网Java工程师面试题·Spring篇·第一弹

目录 1、一般问题 1.1、不同版本的 Spring Framework 有哪些主要功能? 1.2、什么是 Spring Framework? 1.3、列举 Spring Framework 的优点。 1.4、Spring Framework 有哪些不同的功能? 1.5、Spring Framework 中有多少个模块&#xff…

docker和k8s之间的关系

一句话总结:Docker只是容器的一种,它面向的是单体,K8S可以管理多种容器,它面向的是集群,Docker可以作为一种容器方案被K8S管理。 https://baijiahao.baidu.com/s?id1763716289717819767&wfrspider&forpc 背…

input框输入中文时,输入未完成触发事件。Vue中文输入法不触发input事件?

前言 在做搜索输入框时,产品期待实时搜索,就是边输入边搜索,然而对于中文输入法出现的效果,不同的产品可能有不同的意见,有的觉得输入未完成也应该触发搜索。但有的却认为应该在中文输入完成后再触发搜索。我发现在vu…

安全、高效远程访问大数据分析平台解决方法:Splunk Enterprise+Cpolar

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 Splunk Enterprise是一个强大的机器数据管理平台,可帮助客户分析和搜索数据,以及可视化数据…

安装docker ,更换docker版本

docker dockerd & containerd Dockerd(Docker 守护进程)在其底层使用 Containerd 来管理容器。Containerd 是一个开源的容器运行时管理器,由 Docker 公司于2017年开发并开源,它负责实际的容器生命周期管理。 以下是 Docker 守…

Web前端-Vue2+Vue3基础入门到实战项目-Day5(自定义指令, 插槽, 案例商品列表, 路由入门)

自定义指令 基本使用 自定义指令: 自己定义的指令, 可以封装一些dom操作, 扩展额外功能全局注册// 1. 全局注册指令 Vue.directive(focus, {// inserted 会在 指令所在的元素, 被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el)el.focus()} …

hive 问题解决 Class path contains multiple SLF4J bindings

hive输入命令时出现日志冲突提示(问题不复杂,是个warn,强迫症解决,做项目经常遇到,项目里是处理maven。这里处理方法思路类似。) 问题: SLF4J: Class path contains multiple SLF4J bindings. …

【Java集合类面试十二】、HashMap为什么线程不安全?

文章底部有个人公众号:热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享? 踩过的坑没必要让别人在再踩,自己复盘也能加深记忆。利己利人、所谓双赢。 面试官:HashMap为什么线程不安全…

Python Opencv实践 - 车辆统计(2)检测线绘制,车辆数量计数和显示

针对我所使用的视频,对上一节的代码进行了修改,增加了更多参数。 Python Opencv实践 - 车辆统计(1)读取视频,移除背景,做预处理_亦枫Leonlew的博客-CSDN博客示例中的图像的腐蚀、膨胀和闭运算等需要根据具…

通过执行mysql命令查看mysql版本

第一种一条SQL命令搞定 SELECT version() 第二种操作方式 在终端输入命令即可查询 命令:mysql -V 第三种操作方式 在终端输入命令[也就是进入数据库的命令] 命令:mysql -uroot -p 第三种操作方式 需要进入数据库内输入命令进行查询; 命…

逐字稿 | 视频理解论文串讲(下)【论文精读】

1 为什么研究者这么想把这个双流网络替换掉,想用3D 卷积神经网络来做? 大家好,上次我们讲完了上半部分,就是 2D 网络和一些双流网络以及。它们的。变体。今天我们就来讲一下下半部分,就是 3D 网络和 video Transformer…

layui框架实战案例(24):layedit工具栏添加查看源代码按钮的解决方案

layUI框架实战案例系列文章 layui框架实战案例(21):layui上传的哪些事(layui.upload组件、 file文件域、php后台上传)layui框架实战案例(20):常用条件判断和信息展示技巧(图片预览、动态表格、短信已读未读、链接分享、信息脱敏、内置框架页)layui框架实…

企业如何防止文件泄密(图文+视频讲解)

数字化浪潮的推动下,企业文件已经成为企业竞争的核心资产之一。同时,文件泄密事件也频频发生,给企业带来了巨大的经济损失和声誉损失。 那如何有效地防止文件泄密已成为企业亟待解决的重要问题!在查阅了大量资料后,小编…

Linux进程间通信之匿名管道

Linux进程间通信之匿名管道 进程间通信介绍1. 进程间通信的目的2. 进程间通信发展 什么是管道匿名管道1. 什么是匿名管道2. 匿名管道样例详解3. 原理4. 单个父进程与多个子进程管道间通信建立子进程任务相关的头文件主文件的建立执行结果 5. 管道读写规则6. 管道特点 进程间通信…

基于图像识别的跌倒检测算法 计算机竞赛

前言 🔥 优质竞赛项目系列,今天要分享的是 基于图像识别的跌倒检测算法 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…

分布式事务及CAP和BASE定理

一、分布式事务 单体应用肯定就不存在分布式事务了,只有在分布式微服务系统中,各个服务之间通过RPC调用后,每个微服务有自己和数据库的连接,各个微服务的回滚不影响其他的微服务事务,这几必须使用分布式事务来解决分布…