理解浅拷贝和深拷贝以及实现方法

article/2025/10/5 14:14:11

一、数据类型

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型Object,包含(function,Array,Date)。

1、基本数据类型的特点:直接存储在栈内存中的数据
2、引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
在这里插入图片描述

二、浅析栈和堆

通过栈里面定义一个地址值,通过地址值去找堆里面定义的某一个值,
很重要一点是他的栈里是个地址值,地址值指向的是堆,他在堆里面定义的某一个值
相当于拿着号,去堆里面去找,两个号也就是地址值其实是一模一样的

堆跟栈最大的区别是:
1)堆在栈里存了一个地址值
2)栈存储的永远是一个基本数据类型的数据

三、对深浅拷贝理解

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

深拷贝:创建一个新对象,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,则从堆内存中开辟一个新的区域存放该引用类型指向的堆内存中的值,修改新对象的值不会影响原对象。

浅拷贝和深拷贝引用其他博主图片,示意图大致如下:

在这里插入图片描述

在这里插入图片描述
总之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会修改到原对象。

二. 浅拷贝实现方法

1)ES6语法展开运算符…

    const obj1 = {name: "icy",age: 20,hobbies: ["eat", "sleep", "game"],};const obj2 = { ...obj1 }console.log(obj2, 'obj2')

打印结果
在这里插入图片描述
为了验证是浅拷贝,我们改变一下obj2中数组的第一项的值,然后再输出ojb1和obj2:

const obj1 = {name: 'Nancy',age: 18,hobbies: ['eat', 'sleep', 'game']}const obj2 = { ...obj1 }// 修改堆内存中的值obj2.age = 20obj2.hobbies[0] = 'play'console.log('修改后obj2', obj2)console.log('修改前obj1', obj1)

打印结果如下:

在这里插入图片描述

修改数组时候,obj1和obj2都受到了影响,验证了浅拷贝。

2)Object.assign() 方法

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象,返回值为合并后的新对象。但是Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

注意: 当object只有一层的时候,是深拷贝。

  let obj = { username: 'kobe' }let obj2 = Object.assign({}, obj)   //将拷贝对象与{}空对象合并obj2.username = 'wade'console.log(obj,'obj')console.log(obj2,'obj2')

在这里插入图片描述

3)Array.prototype.concat() 方法

该方法用于数组合并,合并的数组.concat(被合并的数组…)
参数可有多个,用逗号分隔,返回合并后的数组。
原理:用原数组去合并一个空数组,返回合并后的数组。

let arr = [1, 3, {username: 'kobe'
}];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
arr2[0] = 555;console.log(arr,'arr');
console.log(arr2,'arr2');

在这里插入图片描述

4)数组剪裁方法 slice()

该方法用途很多,可对数组进行增删,剪裁操作。

 const arr1 = [1, 3, { username: 'kobe' }]const arr2 = arr1.slice()  //返回剪裁后的数组,这里没有剪裁掉任何项,相当于返回原数组// 修改堆内存中的值arr2[0] = 5arr2[2].username = 'wade'console.log('arr1', arr1)console.log('arr2', arr2)

在这里插入图片描述

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

个人感觉,以上四种方法都只有一层的时候才是深拷贝。

5)手写浅拷贝

创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回。

function clone(target) {let cloneTarget = {};for (const key in target) {cloneTarget[key] = target[key];}return cloneTarget;
};

三. 深拷贝实现方法

1)JSON.parse(JSON.stringify())

原理:用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

  const obj1 = {user_info:{name: "Nancy",age: 18,gender: "女",},hobbies: ["eat", "sleep", "game"],}const obj2 = JSON.parse(JSON.stringify(obj1)); obj2.user_info.name = 'Juliet'console.log(obj1,'obj1');console.log(obj2,'obj2')

在这里插入图片描述

注意点:
这种方法虽然简单方便,可以实现数组或者对象的深拷贝。但是不能处理函数正则,因为这两者基于JSON.stringify 和 JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

const obj1 = {name: "Nancy",age: 18,gender: "女",hobbies: ["eat", "sleep", "game"],//函数watchComic: () => {console.log("Nancy 你好");},//正则regx: /^icy{3}$/g,};const obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj2,'obj2');

在这里插入图片描述

可看到上图打印,函数没了,正则变成空对象{}。
这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

2)手写递归方法

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

手动递归实现深拷贝,我们只需要完成以下2点即可:

  1. 对于基本数据类型,我们只需要简单地赋值即可(使用 “=” )
  2. 对于引用类型,我们需要创建新的对象,并通过遍历键来赋值对应的值,这个过程中如果遇到 Object 类型还需要再次进行遍历。(注意Array也属于Object类型)
export function deepCopy (obj1, obj2) {// 深拷贝if (obj2 === undefined) {if (Array.isArray(obj1)) {obj2 = []} else {obj2 = {}}}// 遍历目标数据for (let i in obj1) {if (obj1.hasOwnProperty(i)) {if (Array.isArray(obj1[i])) { //判断目标结构里的每一项值是否存在数组/对象obj2[i] = []deepCopy(obj1[i], obj2[i])} else if (typeof obj1[i] === 'function') {obj2[i] = obj1[i]} else if (obj1[i] instanceof Object) {obj2[i] = {}deepCopy(obj1[i], obj2[i])} else {obj2[i] = obj1[i]  // 这里的值就是基本数据类型了}}}return obj2
}
  const obj1 = {user_info: {name: 'Nancy',age: 18,gender: '女'},hobbies: ['eat', 'sleep', 'game'],// 函数watchComic: () => {console.log('Nancy 你好')}}let obj2 = deepCopy(obj1)obj2.user_info.age = 20obj2.user_info.gender = '男'console.log(obj1, 'obj1')console.log(obj2, 'obj2')

在这里插入图片描述

学习过程中也遇到一些很不错的文章:
彻底讲明白浅拷贝与深拷贝
前端深拷贝与浅拷贝(附实现方法)
谁动了我的数据 | 程序员必备小知识


http://chatgpt.dhexx.cn/article/4aJ644eu.shtml

相关文章

VUE浅拷贝和深拷贝

文章目录 前言一、数据类型1.1.基本数据类型1.2.引用数据类型1.3.区别 二、浅拷贝2.1.定义2.2.浅拷贝特点 三、深拷贝3.1.定义3.2.深拷贝特点 四、拷贝实现方案4.1.Object.assign()4.2.concat()4.3.slice()4.4.JSON.parse(JSON.stringify())4.5.cloneDeep() 五、结论 前言 在理…

C# 中的浅拷贝和深拷贝

在本文中,将通过示例讨论C#中的浅拷贝和深拷贝。这是上一篇文章的续篇。因此,在继续本文之前,请阅读以前的文章,其中之前使用示例讨论了C#中的原型设计模式。 什么是深拷贝和浅拷贝? 浅复制和…

JAVA 浅拷贝和深拷贝

拷贝 拷贝即对已有的数据创建一个副本,在 Java 中,拷贝可分为引用拷贝、浅拷贝、深拷贝。 引用拷贝 在 Java 中,实例化后的对象存储在堆区,而局部变量存放在局部变量表(栈)中,如: public void yinYongC…

js浅拷贝和深拷贝

1、JS数据类型 基本数据类型:Boolean、String、Number、null、undefined 引用数据类型:Object、Array、Function、RegExp、Date等 2、深拷贝与浅拷贝 深拷贝和浅拷贝都只针对引用数据类型, 浅拷贝会对对象逐个成员依次拷贝,但…

C++浅拷贝和深拷贝

1、浅拷贝 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。 举个简单的例子,你的小名叫西西,大名叫…

彻底理解Python中浅拷贝和深拷贝的区别

目录 前言 1. 浅拷贝和深拷贝的概念 2. is和的区别 3. 赋值操作 4. copy模块里面的copy()方法 5. copy模块里面的deepcopy()方法 6.字典自带的copy方法 7.切片表达式拷贝 8.总结 前言 Python 的所有变量其实都是指向内存中的对象的一个指针,这确实和之前学…

如何理解java的回调函数?

对于技术问题,会用是一回事,理解这个技术问题的来龙去脉、设计者当初为什么要设计这个功能、这个技术问题有哪些优势、适用哪些场景又是另外回事了。 前者照猫画虎得其形,后者形神兼备得其意,这也是所谓青铜与王者的区别。 会使…

java使用回调函数

java回调函数 回调函数(callback Function),顾名思义就是用来回调的函数。在两个类A、B中,A在调用B接口的同时B也在调用A 回调函数也常用于线程中的异步获取消息。 举个简单的例子,公司中老板分发任务给员工A&#…

java中回调函数的实现

在java的事务中,有时候可能会遇到以下情况,第一步是更新某某表,中间可能要更新不确定的多步,最后一步是更新缓存,结构大致如下: (1)updateA(); (2)updateXX…

什么是java回调函数

回调函数 一:故事背景二:概念三:回调函数的作用四:java中如何进行回调4.1 类图4.2 定义回调接口4.3 实现回调接口4.4 调用方法使用回调函数4.5 Main函数调用4.6 总结描述 五:回调函数的优点5.1 灵活性5.2 解耦性5.3 异…

简单举例JAVA回调函数的实现

来自维基百科的对回调(Callback)的解释:In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execut…

java回调函数机制

Java回调函数机制 参考了网上的一些资料,下面也做出一些总结,供初学者了解学习。 一、 概述 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调、异步调用 。 同步调用:一…

java中如何实现回调函数

最近工作需要研究了一会别人写的库,其中充满着各种"回调函数",因此把自己理解给记录下来,存档。 首先我们来看看回调函数 这个概念的具体由来,百度百科的示义如下: 回调函数就是一个通过函数指针调用的函数。…

Java回调函数 + 使用案例

文章目录 前言什么是回调函数第0个版本第1个版本第2个版本第3个版本第4个版本第5个版本第6个版本回头解析前言描述的问题1. MethodIntrospector.selectMethods()2. 抽象类MethodIntrospector3. 方法selectMethods()4. 成员变量USER_DECLARED_METHODS5. 方法doWithMethods()6. d…

Java-回调函数

什么是回调 函数调用可以分为三种模式,串行调用、异步调用、回调。这三种都是日常开发中常见到的方式。 一个方法执行完,再执行下一个,串行逻辑会阻塞线程执行流程,等到所有逻辑执行结束,线程才会结束。 异步执行是…

java回调函数(callBack)

最近有个新同事给我写了个接口,说是用到了回调,我等了半天发现结果才返回回来,把我都整急了。最后我看了他的代码,目瞪口呆。他还信誓旦旦的说没错,按网上的例子来写的。 我一搜,网上例子真一大堆&#xff…

java回调函数的作用以及运用

模块之间总是存在这一定的接口,从调用方式上看,可以分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,也是我们在写程序中经常使用的;回调是一种双向的调用模式,也就是说,被调用的…

Java回调函数理解和应用

#Java回调函数理解和应用 所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法,这样子说你是不是有点晕晕的。 在未理解之前,我也是一脸懵逼,等我理解之后&…

java回调函数(全干货)

产生接口回调的场景 产生接口回调的场景很简单,比如A叫B帮忙做一件事,交代完后A去忙别的事,然后B做完这件事之后会通知A, 通知A的这个动作就是接口回调的动作。接口回调 接口回调的意义是通过接口来实现解耦的的前提下调用另一个类的方法,也就是B为A准…

深入理解Java回调函数

废话不多说,像许多网上介绍回调机制的文章一样,我这里也以一个现实的例子开头:假设你公司的总经理出差前需要你帮他办件事情,这件事情你需要花些时间去做,这时候总经理肯定不能守着你做完再出差吧,于是就他…