深拷贝和浅拷贝的区别和与原理

article/2025/9/17 4:26:56

一、基本类型和引用类型

  1. string,number,boolean,null,undefined,symbol
  2. Function,Array,Object
    基本类型是按值访问的,引用类型是按引用访问
    基本类型和引用类型也有人叫原始类型和对象类型,拥有方法的类型和不能拥有方法的类型,可变类型和不可边类型

二、浅拷贝和深拷贝

在这里插入图片描述
如图所示:

obj2是对obj1的浅拷贝,obj2新建了一个对象,但是obj2对象复制的是obj1的指针,也就是obj1的堆内存地址,而不是复制对象本身。obj1和obj2是共用了内存地址的。

obj3是对obj1的深拷贝,obj3和obj1不共享内存

概念:

浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块

深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

三、赋值与浅拷贝

1.赋值

当我们把一个对象赋值给一个变量的时候,赋值的其实是该对象的栈内存地址而不是堆内存数据,(此处看基本类型和引用类型,对象属于引用类型,值分为栈内存的地址和堆内存中的数据)。也就是赋值前的对象和赋值后的对象两个对象共用一个存储空间(赋值的是栈内存地址,而该地址指向了同一个堆内存空间),所以,无论哪个对象发生改变,改变的都是同一个堆堆内存空间。因此,无论修改哪个对象对另一个对象都是有影响的

	var objA ={name:'张三',age:8,pal:['王五','王六','王七']}var objB = objAobjB.name = '李四'objB.pal[0] = '王麻子'console.log('objA.name',objA.name) //李四console.log('objB.name',objB.name)//李四console.log('objB.pal',objB.pal)//['王麻子','王六','王七']console.log('objA.pal',objA.pal)//['王麻子','王六','王七']

总结:赋值后的对象objB改变,原对象objA的值也改变,因为赋值后的对象objB赋值的是原对象objA的栈内存地址,他们指向的是同一个堆内存数据,所以对赋值后的对象objB对数据进行操作会改变公共的堆内存中的数据,所以原对象的值也改变了。

2.浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原对象属性值的一份精准拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

有点抽象,来看个例子,该例子也是手写浅拷贝的方法

	var obj1 ={name:'张三',age:8,pal:['王五','王六','王七']}var obj3 = shallowCopy(obj1)function shallowCopy (src){var newObj = {};for(var prop in src ){console.log(prop)if(src.hasOwnProperty(prop)){newObj[prop] = src[prop]}}return newObj}obj3.name = '李四'obj3.pal[0] = '王麻子'console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']}console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}

obj3改变了基本类型的值name,并没有使原对象obj1的name改变,obj3改变了引用类型的值,导致原对象的值也改变了

总结:

赋值:就是对原对象的栈内存地址进行复制

浅拷贝:是对原对象的属性值进行精准复制,如果原对象的属性值是基本类型,那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候会修改到原对象。

因此一般对无引用类型的属性的兑现拷贝的时候使用浅拷贝就行,对复杂对象包含引用类型属性的时候使用深拷贝

四、浅拷贝的实现方式

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

原对象属性中包含引用类型:进行了浅拷贝,拷贝了原对象属性值,所以拷贝的对象改变的时候原对象的引用类型也改变

	var obj1 ={name:'jack',age:25,hobby:{ball:'tennis'}}let obj2 = Object.assign({},obj1)obj2.hobby.ball = 'basketball'console.log('obj1',obj1.hobby.ball) //basketballconsole.log('obj2',obj2.hobby.ball) //basketball

原对象属性中不包含引用类型的时候等价于深拷贝,因为不包含引用类型的时候是对属性值的拷贝也就是对基本类的值的复制,也就是值引用,所以对拷贝的对象改变不会影响到原对象,也就等价于深拷贝

var obj1 ={name:'jack',age:25,}let obj2 = Object.assign({},obj1)obj2.name = 'rose'console.log('obj1',obj1.name) //jackconsole.log('obj2',obj2.name) //rose

对于数组的浅拷贝

2…Array.prototype.slice()

 var arr = ['jack',25,{hobby:'tennise'}];let arr1 = arr.slice()arr1[2].hobby='rose'arr1[0]='rose'console.log( arr[2].hobby) //roseconsole.log( arr[0]) //jack

3.Array.prototype.concat()

var arr = ['jack',25,{hobby:'tennise'}];let arr1 = arr.concat()arr1[2].hobby='rose'arr1[0]='rose'console.log( arr[2].hobby) //roseconsole.log( arr[0]) //jack

4.解构

let aa = {age: 18,name: 'aaa',address: {city: 'shanghai'}
}let bb = {...aa};
bb.address.city = 'shenzhen';console.log(aa.address.city);  // shenzhen

五、深拷贝的实现方法

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

var arr = ['jack',25,{hobby:'tennise'}];let arr1 = JSON.parse(JSON.stringify(arr))arr1[2].hobby='rose'arr1[0]='rose'console.log( arr[2].hobby) //tenniseconsole.log( arr[0]) //jack

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

缺点:当对象里面有函数的话,深拷贝后,函数会消失

2.手写递归函数实现深拷贝

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

var obj = {   //原数据,包含字符串、对象、函数、数组等不同的类型name:"test",main:{a:1,b:2},fn:function(){},friends:[1,2,3,[22,33]]}function copy(obj){let newobj = null;   //声明一个变量用来储存拷贝之后的内容//判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可,//由于null不可以循环但类型又是object,所以这个需要对null进行判断if(typeof(obj) == 'object' && obj !== null){ //声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存newobj = obj instanceof Array? [] : {};   //循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数for(var i in obj){  newobj[i] = copy(obj[i])}}else{newobj = obj}    console.log('77',newobj)return newobj;    //函数必须有返回值,否则结构为undefined}var obj2 = copy(obj)obj2.name = '修改成功'obj2.main.a = 100console.log(obj)console.log(obj2)

3.借助第三方库lodash

// 安装lodashnpm i --save lodash// 引入lodash var _ = require('lodash');var obj1 ={name:'jack',age:25,}let obj2 =_.cloneDeep(obj1)obj2.name = 'rose'console.log('obj1',obj1.name) //jackconsole.log('obj2',obj2.name) //rose

http://chatgpt.dhexx.cn/article/s6JnuLNq.shtml

相关文章

浅拷贝、深拷贝

深拷贝和浅拷贝 这两个概念是在项目中比较常见的,在很多时候,都会遇到拷贝的问题,我们总是需要将一个对象赋值到另一个对象上,但可能会在改变新赋值对象的时候,忽略掉我是否之后还需要用到原来的对象,那么就…

深拷贝和浅拷贝(copy和deepcopy)详解

深拷贝和浅拷贝(copy和deepcopy)详解 详细解释存储方式列表的增删改列表修改已有值列表新增一个值列表整体重新赋值 copy与deepcopy的区别不可变类型可变类型 浅拷贝指的是创建一个新对象,其中包含原始对象的引用(指针&#xff09…

浅拷贝和深拷贝的区别?

创建Java对象的方式包括new、反射、反序列化、拷贝,那么什么是拷贝呢?浅拷贝和深拷贝又有什么区别呢? 什么是拷贝 拷贝就是为了复用原对象的部分或全部数据,在原对象的基础上通过复制的方式创建一个新的对象。 Object类中有nati…

深拷贝和浅拷贝的区别

首先,明确一点深拷贝和浅拷贝是针对对象属性为对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会…

浅拷贝与深拷贝的区别

浅拷贝与深拷贝 一、数据类型 数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。 基本数据类型的特点:直接存储在栈(stack)中的数据 引用数据类型的特点:存储的是该对象在栈中引用,真实的…

堆、栈和队列

1. 堆 堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。堆是指程序运行时申请的动态内存,而栈只是指一种使用堆的方法(即先进后出)。 2. 栈(stack&…

堆、栈和队列的区别

目录 数据结构中的堆、栈和队列 内存申请中的堆和栈 一个C/C程序占用的内存如下: 申请内存后的响应 申请大小的限制 申请效率的比较 堆和栈中的存储内容 数据结构中的堆、栈和队列 堆:堆是一种经过排序的树形数据结构,每个结点都有…

栈和队列

文章目录 栈栈操作队列队列操作双端队列双端队列操作 栈 栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标&…

栈和队列简介

栈和队列简介 栈和队列是两种常用的数据结构,它们的数据是按线性结构存储的,因此,栈和队列也属于线性表。 栈和队列的数据可以存储在一个顺序表里,也可以存储在一个链表里,只要满足线性存储结构就行。只对数据的线性…

栈和队列基本操作

栈和队列 一、栈栈是什么栈的实现栈的基本操作栈类型的定义初始化栈检查栈是否为满入栈出栈获取栈顶元素检测栈是否为空测试函数 完整代码Stack.hStack.cmain.c 二、队列队列是什么队列的实现队列类型的定义申请一个节点初始化队列队尾入队列队头出队列获取队列头部元素获取队列…

C#队列和栈的使用

一、队列 队列是其元素以先进先出&#xff08;Firstin,Firstout,FIFO&#xff09;的方式来处理的集合。先放入队列中的元素会先读取。队列使用System.Collections.Generic命名空间中的泛型类Queue<T>实现。 队列的成员 Count&#xff1a;Count属性返回队列中元素个数。…

栈和队列经典面试题

目录 1、括号匹配问题 2、用队列实现栈 3、用栈实现队列 4、设计循环队列 1、括号匹配问题 链接直达&#xff1a; 有效的括号 题目&#xff1a; 思路&#xff1a; 做题前&#xff0c;得先明确解题方案是啥&#xff0c;此题用栈的思想去解决是较为方便的&#xff0c;栈明确指…

栈stack和队列

栈和队列 一 栈和队列二 栈三.队列 一 栈和队列 栈和队列是两种重要的线性结构。从数据结构来看&#xff0c;栈和队列也是线性表&#xff0c;其特殊性在于栈和队列的基本操作是线性表操作的子集&#xff08;也具有顺序结构和链式结构&#xff09;&#xff0c;它们是操作受限的…

链表,队列和栈的区别

链表&#xff0c;队列和栈都是数据结构的一种。Sartaj Sahni 在他的《数据结构、算法与应用》一书中称&#xff1a;“数据结构是数据对象&#xff0c;以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。”他将数据对象&#x…

栈与队列的定义与区别

1、栈 首先&#xff0c;普通的线性表实现是有两个端口可以访问的&#xff0c;但是如果作为栈就要封闭一端&#xff0c;只能访问另一端。这当然不是自讨苦吃&#xff0c;栈是一种抽象数据结构&#xff0c;是对现实世界对象的模拟。比如&#xff0c;自助餐厅中的一叠盘子&#x…

java 队列和栈的区别

栈和队列的区别 &#xff08;1&#xff09;数据插入删除 栈是一种特殊的线性表&#xff0c;他只能在一段进行插入和删除操作&#xff0c;就好像是一个井一样。进行数据插入和删除就类似于井口&#xff0c;称为栈定。而井也是有底部的&#xff0c;栈无法进行插入删除操作的这一…

监督学习和无监督学习的区别(机器学习)

机器学习主要分为两类 监督学习无监督学习 两者的区别主要是是否需要人工参与数据结果的标注 监督学习&#xff1a;教计算机如何去完成预测任务&#xff08;有反馈&#xff09;&#xff0c;预先给一定数据量的输入和对应的结果即训练集&#xff0c;建模拟合&#xff0c;最后让…

简单说下有监督学习和无监督学习的区别

简单说下有监督学习和无监督学习的区别 解析&#xff1a; 有监督学习&#xff1a;对具有标记的训练样本进行学习&#xff0c;以尽可能对训练样本集外的数据进行分类预测。&#xff08;LR,SVM,BP,RF,GBDT&#xff09; 无监督学习&#xff1a;对未标记的样本进行训练学习&#xf…

一个简单的例子来理解监督学习和非监督学习及其区别

首先&#xff0c;必须理解两个基本概念&#xff1a;特征值和目标值&#xff0c;先看图例 1、特征值&#xff1a; 特征值是指数据的特征&#xff0c;对于每个样本&#xff0c;通常具有一些 "属性"&#xff08;Attribute&#xff09;或者说 ”特征“&#xff08;Featu…

监督学习、无监督学习和半监督学习区别

1、概念 1.1监督学习&#xff08;数据集有输入和输出数据&#xff09;&#xff1a;通过已有的一部分输入数据与输出数据之间的相应关系。生成一个函数&#xff0c;将输入映射到合适的输出&#xff0c;比如分类。 1.2无监督学习&#xff08;数据集中只有输入&#xff09;&…