js关于深度克隆问题

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();  //1y();  //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); // truenewObj.c.d = 100 //改变newObjconsole.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 flagswitch (type) {case 'Array':flag = typeString === '[object Array]'breakcase 'Date':flag = typeString === '[object Date]'breakcase 'RegExp':flag = typeString === '[object RegExp]'breakdefault: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 valueif (type !== 'object' || type === null) return valueif (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 = oldObjdeepClone(oldObj).then(v => {console.log(v)console.log(newObj == oldObj)})

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

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

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

相关文章

app拉新渠道整合 一手地推、网推拉新平台整理

1.聚量推客 聚量推客自己本身是服务商,自己直营的平台,相对来说数据更好,我们也拿到了平台首码:000000 填这个就行,属于官方渠道 2.蓝猫推客 蓝猫推客我认为是比较又潜力的平台,经过几天测试数据和结算都…

网络规划设计

文章目录 一、网络架构设计1.1 单核心双核心1.1.1 单核心1.1.2 双核心端口聚合 链路聚合网关冗余VRRP堆叠技术生成树STPPOE以太网供电服务器冗余&负载均衡服务器双机热备 1.2 环网架构设计1.3 层次化架构设计1.3.2 三层网络架构设计1.3.3 园区大二层网络架构1.3.4 数据中心…

最详细STM32,cubeMX串口发送,接收数据

这篇文章将详细介绍 串口 发送数据,接受数据。 文章目录 前言一、串口的基础知识二、cubeMX 配置三、自动生成代码解析四、串口发送数据函数五、使用串口收发数据点亮 led重定向函数: 总结 前言 实验开发板:STM32F103C8T6。所需软件&#xf…

【TES605】基于Virtex-7 FPGA的高性能实时信号处理平台

板卡概述 TES605是一款基于Virtex-7 FPGA的高性能实时信号处理平台,该平台采用1片TI的KeyStone系列多核DSP TMS320C6678作为主处理单元,采用1片Xilinx的Virtex-7系列FPGA XC7VX690T作为协处理单元,具有2个FMC子卡接口,各个处理节…

景联文科技:针对敏感数据的安全转录服务,护航信息安全

针对数据的安全转录服务,主要是为了确保数据在转录过程中的安全性和隐私保护。这些服务通常会采用一系列严格的安全措施,如数据加密、访问控制、数据脱敏等,以确保敏感数据不会被泄露或滥用。 景联文科技提供特定的数据转录服务,以…

CentOS7.9+Kubernetes1.28.3+Docker24.0.6高可用集群二进制部署

CentOS7.9Kubernetes1.28.3Docker24.0.6高可用集群二进制部署 查看版本关系 ## 从kubernetes-server-linux-amd64.tar.gz解压后有kubeadm ]# ./kubeadm config images list W1022 20:06:05.647976 29233 version.go:104] could not fetch a Kubernetes version from the in…

WPF Material Design UI框架

前言 Material Design in xaml 是开源免费的ui框架&#xff0c;工控软件主打的就是简单界面。 以下简称MD 相关资源 MaterialDesignInXamlToolkit Github 地址 MD 快速启动 MD 案例压缩包 MD 框架使用 启动环境配置 安装Nuget包 App.xaml 配置 <Application x:Class&qu…

leetcode:面试题 17.04. 消失的数字(找单身狗/排序/公式)

一、题目&#xff1a; 函数原型&#xff1a;int missingNumber(int* nums, int numsSize) 二、思路&#xff1a; 思路1 利用“找单身狗”的思路&#xff08;n^n0&#xff1b;0^nn&#xff09;&#xff0c;数组中有0-n的数字&#xff0c;但缺失了一个数字x。将这些数字按位异或0…

zookeeper源码(02)源码编译启动及idea导入

本文介绍一下zookeeper-3.9.0源码下载、编译及本地启动。 下载源码 git clone https://gitee.com/apache/zookeeper.gitcd zookeeper git checkout release-3.9.0 git checkout -b release-3.9.0源码编译 README_packaging.md文件 该文件介绍了编译zookeeper需要的环境和命…

PKU 概率论+数理统计+建模 期中考复习总结

目录 计算条件概率计算概率&#xff08;放回与不放回&#xff09;生成随机数算法Linear Congruential Method判断是否是full period Uniformity (test of frequency)1.Chi-Square testmethodreminderexample 2.Kolmogorov-Sminov testmethodexample Independence (test of auto…

决策树-入门

1、认识决策树 决策树思想的来源非常朴素&#xff0c;程序设计中的条件分支结构就是if-then结构&#xff0c;最早的决策树就是利用这类结构分割数据的一种分类学习方法 怎么理解这句话&#xff1f;通过一个对话例子 想一想这个女生为什么把年龄放在最上面判断&#xff01;&a…

WebSocket—STOMP详解(官方原版)

WebSocket协议定义了两种类型的消息&#xff08;文本和二进制&#xff09;&#xff0c;但其内容未作定义。该协议定义了一种机制&#xff0c;供客户端和服务器协商在WebSocket之上使用的子协议&#xff08;即更高级别的消息传递协议&#xff09;&#xff0c;以定义各自可以发送…

【Java笔试强训】Day4(WY33 计算糖果、DD5 进制转换)

WY33 计算糖果 链接&#xff1a;WY33 计算糖果 题目&#xff1a; A,B,C三个人是好朋友,每个人手里都有一些糖果,我们不知道他们每个人手上具体有多少个糖果,但是我们知道以下的信息&#xff1a; A - B, B - C, A B, B C. 这四个数值.每个字母代表每个人所拥有的糖果数. 现…

Win10/Win11系统bitlocker正在等待激活如何解决?

有同学升级Win10系统后&#xff0c;发现C盘与D盘分区盘符中出现了黄色的锁定感叹号&#xff0c;还显示“bitlocker正在等待激活”&#xff0c;这可能是用户开启了bitlocker加密所导致的。下面就来看看解决的办法吧。 一、bitlocker正在等待激活的解决方法 打开控制面板-系统和安…

.obj模型文件(带材质和纹理)合并的基本思路

1、将v开头的顶点信息依次拷贝到合并新.obj中 2、将vt纹理坐标依次拷贝到合并新.obj中 3、f&#xff08;面&#xff09;的合并 步骤&#xff1a; &#xff08;1&#xff09;第一个obj文件的f&#xff08;面&#xff09;原封不动拷进新.obj中 &#xff08;2&#xff09;第二个…

【虚幻引擎UE】UE4/UE5 基于2D屏幕坐标获取场景3D坐标 射线检测(蓝图/C++)

UE4/UE5 基于2D屏幕坐标获取场景3D坐标 一、射线检测1&#xff09;定义1&#xff09;射线与3D场景中的物体交互的流程2&#xff09;射线检测蓝图函数3&#xff09;蓝图实现根据鼠标点击位置获取场景中的坐标值4&#xff09;根据相机中心点获取场景中的坐标值5&#xff09;射线检…

MSQL系列(七) Mysql实战-SQL语句Join,exists,in的区别

Mysql实战-SQL语句Join&#xff0c;exists&#xff0c;in的区别 前面我们讲解了索引的存储结构&#xff0c;BTree的索引结构&#xff0c;以及索引最左侧匹配原则及讲解一下常用的SQL语句的优化建议&#xff0c;今天我们来详细讲解一下 我们经常使用的 join&#xff0c; exist&…

进程之操作系统的概念

再小的努力&#xff0c;乘以365都很明显。文章目录 操作系统操作系统的概念设计操作系统的目的 管理 ps:如何理解管理如何进行管理 操作系统管理软硬件资源小总结系统调用和库函数的概念小总结 操作系统 在讲述进程的时候我们先讲述一下操作系统&#xff08;os&#xff09;,因…

VR全景平台应该具备哪些功能,怎样选择VR全景平台

引言&#xff1a; 虚拟现实&#xff08;VR&#xff09;技术在不断发展&#xff0c;为企业和消费者提供了全新的交互体验。VR全景平台是一个关键的组成部分&#xff0c;它必须具备一系列重要的功能来吸引用户、提供内容和实现商业成功。那么VR全景平台应该具备哪些功能&#xf…

数据分析和机器学习的11个高级可视化图表介绍

可视化是一种强大的工具&#xff0c;用于以直观和可理解的方式传达复杂的数据模式和关系。它们在数据分析中发挥着至关重要的作用&#xff0c;提供了通常难以从原始数据或传统数字表示中辨别出来的见解。 可视化对于理解复杂的数据模式和关系至关重要&#xff0c;我们将介绍11…