手写深拷贝(Deep Copy)和浅拷贝(Shallow Copy)

在编程中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两个重要的概念,特别是在处理对象或数组时。它们的主要区别在于如何处理对象或数组中的引用类型(如对象、数组等)。

浅拷贝(Shallow Copy)

浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

在JavaScript中,我们可以使用Object.assign()方法或展开运算符(...)来进行浅拷贝。

使用Object.assign()进行浅拷贝

let obj1 = {a: 1,b: { c: 2 }
};let obj2 = Object.assign({}, obj1);obj2.b.c = 3;console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

深拷贝(Deep Copy)

深拷贝会创建一个新的对象,并递归地复制对象的所有属性及其子对象,直到它们都是基本类型为止。这样,新对象与原始对象没有任何关联。

在JavaScript中,没有内置的函数可以直接进行深拷贝,但我们可以使用JSON方法(但这种方法有局限性,比如不能处理函数和循环引用)或者手动实现一个深拷贝函数。

使用JSON方法进行深拷贝(有局限性)

let obj1 = {a: 1,b: { c: 2 }
};let obj2 = JSON.parse(JSON.stringify(obj1));obj2.b.c = 3;console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

JSON.stringify()JSON.parse() 的上下文中,不能处理函数和循环引用指的是:

  1. 函数(Functions):
    当您尝试使用 JSON.stringify() 将一个包含函数的 JavaScript 对象转换为 JSON 字符串时,该函数将不会被转换为字符串的一部分。在 JSON 规范中,函数不是有效的数据类型,因此它们会被忽略。如果您在对象中有一个函数属性,并且您尝试将其转换为 JSON 字符串,那么这个函数属性将不会出现在生成的字符串中。
    例如:
const obj = {name: "John",greet: function() {console.log("Hello, " + this.name);}
};const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"name":"John"}


如上所示,greet 函数没有出现在输出的 JSON 字符串中。

  1. 循环引用(Circular References):
    在 JavaScript 中,对象可以通过属性相互引用,形成循环引用。当您尝试使用 JSON.stringify() 转换包含循环引用的对象时,它会抛出一个错误,因为 JSON 格式不支持循环引用。
    例如:
const obj1 = {};
const obj2 = { ref: obj1 };
obj1.alsoRef = obj2;try {const jsonString = JSON.stringify(obj1);
} catch (error) {console.error(error); // 抛出错误,因为存在循环引用
}


在这个例子中,obj1obj2 通过 refalsoRef 属性相互引用,形成了一个循环。当您尝试使用 JSON.stringify() 转换这个对象时,它会抛出一个错误。
为了避免循环引用的问题,您可以使用一个 replacer 函数来排除或处理循环引用。但是,请注意,即使您使用 replacer 函数,您也无法在 JSON 字符串中保留循环引用的结构,因为 JSON 格式本身不支持这种结构。
对于 JSON.parse() 来说,它不会遇到循环引用的问题,因为它只是将有效的 JSON 字符串转换回 JavaScript 对象。但是,如果您从某个源(如服务器)接收到包含无效循环引用的 JSON 字符串,并且尝试使用 JSON.parse() 解析它,那么它将抛出一个 SyntaxError。在正常的 JSON 字符串中,您不会遇到循环引用,因为 JSON 格式不支持它们。


手动实现深拷贝(递归方法)

function deepCopy(obj, hash = new WeakMap()) {if (typeof obj !== 'object' || obj === null) {return obj;}if (hash.has(obj)) {return hash.get(obj);}let copy = Array.isArray(obj) ? [] : {};hash.set(obj, copy);for (let key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopy(obj[key], hash);}}return copy;
}let obj1 = {a: 1,b: { c: 2 }
};let obj2 = deepCopy(obj1);obj2.b.c = 3;console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

这个手动实现的深拷贝函数可以处理对象和数组,并且可以处理循环引用。它使用了一个WeakMap来存储已经拷贝过的对象,以便在遇到循环引用时能够返回正确的拷贝。

手写深拷贝

deepClone 函数是一个实现了深拷贝功能的函数,它递归地遍历对象并复制其属性,包括数组和嵌套对象。同时,添加了递归深度的打印以及属性的克隆过程,这有助于理解函数是如何工作的。

简要分析:

  1. deepClone 函数接受两个参数:obj(要克隆的对象)和 depth(当前递归的深度,默认为0)。
  2. 使用 console.log 打印当前递归的深度和被克隆的对象,这对于调试和理解函数执行过程非常有用。
  3. 检查 obj 是否不是对象或是否为 null,如果是,则直接返回 obj 本身(基本数据类型和 null 的值传递)。
  4. 初始化 result 变量,根据 obj 的类型(数组或对象)来创建一个新的空数组或空对象。
  5. 使用 for...in 循环遍历 obj 的所有可枚举属性。
  6. 使用 hasOwnProperty 方法检查属性是否是 obj 自身的属性(而不是继承自原型链的属性)。
  7. 对于每个属性,递归调用 deepClone 函数来克隆属性的值,并将结果存储在新的 result 对象中。
  8. 返回 result,即克隆后的对象。

在示例使用部分,您创建了一个包含各种类型属性的对象 original,并使用 deepClone 函数克隆了它。然后,您打印了原始对象和克隆后的对象,以验证深拷贝是否成功。

执行这段代码,您应该会在控制台看到递归深度和对象被克隆的过程,以及原始对象和克隆后的对象的内容。由于 deepClone 函数实现了深拷贝,所以原始对象和克隆后的对象在内存中是独立的,修改其中一个不会影响另一个。

基础版本

会写基础版本的就够了,对于有工作经验的人还要考虑Map,Sety以及循环引用

function deepClone(obj, depth = 0) {console.log('depth:', depth, 'value:', obj);if (typeof obj !== 'object' || obj === null) {return obj;}let result;if (Array.isArray(obj)) {result = [];} else {result = {};}for (let key in obj) {if (obj.hasOwnProperty(key)) {const value = obj[key];console.log(`Cloning property ${key}`)result[key] = deepClone(value, depth + 1)}}return result;
}
// 示例使用
const original = {number: 1,bool: true,str: 'string',array: [1, 2, 3],obj: { child: 'child', father: { child_1: 'father_1' } }
};const cloned = deepClone(original);//   console.log('Original:', original);
console.log('Cloned:', cloned);
PS D:\练\js\手写\13-深拷贝> node .\lian.js\
depth: 0 value: {number: 1,bool: true,str: 'string',array: [ 1, 2, 3 ],obj: { child: 'child', father: { child_1: 'father_1' } }
}
Cloning property number
depth: 1 value: 1
Cloning property bool
depth: 1 value: true
Cloning property str
depth: 1 value: string
Cloning property array
depth: 1 value: [ 1, 2, 3 ]
Cloning property 0
depth: 2 value: 1
Cloning property 1
depth: 2 value: 2
Cloning property 2
depth: 2 value: 3
Cloning property obj
depth: 1 value: { child: 'child', father: { child_1: 'father_1' } }
Cloning property child
depth: 2 value: child
Cloning property father
depth: 2 value: { child_1: 'father_1' }
Cloning property child_1
depth: 3 value: father_1
Cloned: {number: 1,bool: true,str: 'string',array: [ 1, 2, 3 ],obj: { child: 'child', father: { child_1: 'father_1' } }
}

提供的 deepClone 函数虽然能够处理大部分基本的深拷贝场景,但它确实有一些潜在的缺陷和限制:

  1. 循环引用:该函数没有处理循环引用的逻辑。如果对象中存在循环引用(即对象属性直接或间接地引用了自己),函数会陷入无限递归,导致栈溢出错误。
  2. 特殊类型:该函数没有处理如 Date、RegExp、Function、Error、Map、Set、BigInt、Symbol 等特殊类型的对象。这些类型的对象在直接复制时可能无法保持其原始状态或行为。
  3. 性能:对于非常大的对象或深度嵌套的对象,递归可能会导致性能问题。虽然现代JavaScript引擎对递归做了优化,但在某些情况下,使用循环而非递归可能会更有效。
  4. 不可枚举属性:for...in 循环只遍历对象自身的可枚举属性。如果对象有不可枚举的属性(例如通过 Object.defineProperty 定义的属性),这些属性将不会被复制到新对象中。
  5. getter/setter:如果对象的属性是通过 getter/setter 方法定义的,那么简单地复制属性值可能不是您想要的行为。您可能希望在新对象上也保持这些 getter/setter 方法。
  6. Buffer 和其他类型化数组:如果对象包含 Node.js 中的 Buffer 或其他类型化数组(如 Uint8Array),则简单的复制可能不会按预期工作。
  7. 原型链:该函数只复制了对象自身的属性,而没有复制原型链上的属性。在某些情况下,您可能希望保持原型链的完整性。
  8. 未考虑 null undefined 作为对象属性的值:虽然函数处理了 null 和 undefined 作为整体输入的情况,但如果它们作为对象的属性值出现(例如 { prop: null } 或 { prop: undefined }),则会被正常复制。但在某些情况下,您可能希望对这些值进行特殊处理。

为了解决这些问题,您可能需要扩展 deepClone 函数以包含额外的逻辑来处理上述特殊情况。另外,也有一些现成的库(如 lodash 的 _.cloneDeep 方法)提供了更强大和灵活的深拷贝功能。

手写浅拷贝

手写浅拷贝(Shallow Copy)通常指的是复制对象的顶层属性,而不是递归地复制对象的所有子属性。在 JavaScript 中,浅拷贝可以通过多种方式实现,包括使用扩展运算符(...)、Object.assign() 方法,或者通过循环遍历对象的属性。
以下是几种实现浅拷贝的方法:

1. 使用扩展运算符(Spread Operator)

function shallowCopy1(obj) {  return {...obj};  
}

2. 使用 Object.assign() 方法

function shallowCopy2(obj) {  return Object.assign({}, obj);  
}

3. 使用循环遍历属性

function shallowCopy3(obj) {  if (typeof obj !== 'object' || obj === null) {  return obj;  }  let copy = Array.isArray(obj) ? [] : {};  for (let key in obj) {  if (obj.hasOwnProperty(key)) {  copy[key] = obj[key];  }  }  return copy;  
}

请注意,这些方法在处理数组、对象以及基本类型时表现良好,但它们都是浅拷贝,这意味着如果对象的属性值是另一个对象或数组,那么新对象和原对象将引用相同的子对象或数组。

示例

const original = {  a: 1,  b: { c: 2 },  d: [3, 4]  
};  const copy1 = shallowCopy1(original);  
const copy2 = shallowCopy2(original);  
const copy3 = shallowCopy3(original);  // 修改原始对象的子对象或数组  
original.b.c = 3;  
original.d.push(5);  // 浅拷贝的对象也会受到影响,因为它们引用了相同的子对象或数组  
console.log(copy1.b.c); // 输出 3  
console.log(copy2.d); // 输出 [3, 4, 5]  
console.log(copy3.b.c); // 输出 3

在这个示例中,由于浅拷贝,copy1、copy2 和 copy3 的 b 属性和 d 属性分别引用了与 original 相同的对象和数组。因此,当修改 original 的这些属性时,浅拷贝的对象也会受到影响。

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

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

相关文章

《Windows API每日一练》8.5 listbox控件

列表框是将一批文本字符串显示在一个具有滚动功能的方框中的控件。通过发送消息到列表框的窗口过程,程序可以添加或删除列表中的字符串。当列表框中的一个项目被选中时,列表框控件便发送 WM_COMMAND消息到其父窗口。然后父窗口确定哪个项目被选中。 本节…

2万字长文详解Ambari面试题及参考答案

目录 Ambari的主要功能是什么? Ambari如何与Hadoop生态系统中的其他组件交互? 解释Ambari中“蓝本”(Blueprints)的概念。 如何使用Ambari进行集群的监控和管理? Ambari支持哪些Hadoop版本? 在Ambari中,如何查看和管理服务日志? Ambari的安装过程涉及哪些主要步骤…

WEB开发-HTML页面更新部分内容

1 需求 2 接口 3 示例 在HTML页面中,如果你想要改变部分内容而不是整个页面,有几种方法可以实现这一目标,主要包括: JavaScript 的 DOM 操作 JavaScript允许你动态地修改HTML文档中的元素内容。你可以使用document.getElementB…

J024_打印电影的全部信息

一、需求描述 展示多部电影的信息。 电影信息包括:电影名称、电影得分、电影票价格。 二、代码实现 2.1 Movie类 package com.itheima.collection;public class Movie {//电影名称private String name;//电影得分private int score;//电影票价格private double…

11-云服务器处理单细胞转录组数据

目录 安装R及相关包 安装 shiny 进行安装包 安装BiocManager 安装Seurat包 网页端Rstudio需打开8787端口 Ubuntu上安装R包linux库缺失 关于服务器配置及相关处理可见:linux学习笔记_hx2024的博客-CSDN博客 安装R及相关包 8-阿里云服务器 ECS配置R及Studio Server-CS…

【Unity 3D角色移动】

【Unity 3D角色移动】 在Unity 3D中实现角色移动通常涉及到几个关键步骤,包括设置角色的物理属性、处理输入、更新角色的位置以及动画同步。下面是实现基本3D角色移动的步骤和示例代码: 步骤1:设置角色的物理属性 角色通常使用Character Co…

OpenCV杂记(4):OpenCV之色彩映射(伪彩applyColorMap)

1. 简述 我们在开发基于热成像(红外)或者做深度估计应用时,为了便于直观的观察,常常将检测结果进行色彩上的映射,这样便可以很直观的看出哪里温度高,哪里温度低,或者哪里深度更深或更浅。 我们将…

数列结构(3.9)——队列应用

树的层次遍历 树的层次遍历,也称为树的广度优先遍历,是一种按照树的层次顺序,从上到下、从左到右遍历树中所有节点的算法。在二叉树中,这种遍历方式通常使用队列来实现。下面是层次遍历的基本步骤: 创建一个空队列&a…

为什么酱酒的标准酒度是53°?

在中国白酒家族中,酱香型白酒以其独特的风味与卓越品质备受推崇,而其标准度数——53度,则是这一酒种标志性特征之一。为什么酱酒的标准酒度是53?酱酒亮哥yutengtrade搜集相关资料总结如下: 科学原理 分子缔合稳定性&a…

Golang | Leetcode Golang题解之第220题存在重复元素III

题目: 题解: func getID(x, w int) int {if x > 0 {return x / w}return (x1)/w - 1 }func containsNearbyAlmostDuplicate(nums []int, k, t int) bool {mp : map[int]int{}for i, x : range nums {id : getID(x, t1)if _, has : mp[id]; has {retu…

java中反射(Reflection)的4个作用

java中反射(Reflection)的4个作用 作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断任意一个类所具有的成员变量和方法作用4、在运行时调用任意一个对象的方法总结 💖The Begin💖…

【Android】自定义换肤框架05之Skinner框架集成

引入依赖 api("io.github.hellogoogle2000:android-skinner:1.0.0")初始化Skinner 在所有功能前调用即可,建议在Application中初始化 SkinnerKit.init(application)安装皮肤包 在应用该皮肤包前安装即可,建议预安装,或应用皮肤…

ABAP中MESSAGE_TEXT_BUILD函数的详细使用方法

MESSAGE_TEXT_BUILD 是 ABAP 中的一个函数,用于根据给定的消息类和消息号构建一个完整的消息文本。这个函数通常与消息类(如 ‘E’, ‘W’, ‘I’, ‘S’, ‘A’ 等)和消息号一起使用,这些通常定义在数据字典(Transact…

扩散模型笔记2

Ref:扩散模型的原理及实现(Pytorch) 在扩散模型中,每一步添加的噪声并不是完全一样的。具体来说,噪声的添加方式和量在每一步是根据特定的规则或公式变化的。这里我们详细解释每一步添加噪声的过程。 正向过程中的噪声添加&…

vb.netcad二开自学笔记9:界面之ribbon

一个成熟的软件怎么能没有ribbon呢&#xff0c;在前面的框架基础上再加个命令AddRibbon <CommandMethod("AddRibbon")> Public Sub AddRibbon() Dim ribbonControl As RibbonControl ComponentManager.Ribbon Dim tab As RibbonTab New RibbonTab() tab.Tit…

Linux 高级 Shell 脚本编程:掌握 Shell 脚本精髓,提升工作效率

【Linux】 高级 Shell 脚本编程&#xff1a;掌握 Shell 脚本精髓&#xff0c;提升工作效率 Shell 脚本编程是 Linux 系统管理员和开发人员的必备技能。通过学习高级 Shell 脚本编程&#xff0c;你可以编写更高效、更灵活和更易于维护的脚本。本文将介绍 Shell 脚本编程中的函数…

初中化学知识点总结(人教版)

第一单元 走进化学世界 一 物质的变化和性质 1物理变化&#xff1a;没有生成其它物质的变化叫做物理变化。 化学变化&#xff1a;生成其他物质的变化叫做化学变化&#xff0c;又叫化学反应。 物理变化和化学变化的区别&#xff1a;是否有其他物质生产。 2化学变化的基本特…

Python - 自动化办公,将yml根据转换规则转换成‘‘ = ‘‘

文章目录 前言## Python - 自动化办公&#xff0c;将yml根据转换规则转换成 1. 准备工作2. demo3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会…

【教程】新的Selenium!整合了隐藏浏览器指纹等功能

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 前景提要 driver Driver() 常用driver 接口 最后的话 前景提要 新的selenium&#xff0c;整合了隐藏浏览器指纹&#xff0c;非常好用&#x…

算法库应用--KMP算法解决串匹配问题

学习来源 学习贺利坚老师博客 数据结构例程——串的模式匹配&#xff08;KMP算法&#xff09;_数据结构模式匹配例题-CSDN博客 本人引导博客 串的匹配 (KPM算法由来引导)_kpm匹配失败-CSDN博客 转载大佬sofu博客 https://www.cnblogs.com/dusf/p/kmp.html 本人详细思路引导b战…