首页 > 其他分享 >深拷贝和浅拷贝的区别和与原理

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

时间:2023-06-22 20:34:37浏览次数:42  
标签:obj1 console name 区别 对象 原理 拷贝 log

感谢参考文章:https://blog.csdn.net/weixin_45753447/article/details/124290929

一、基本类型和引用类型

  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 = objA
	objB.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) //basketball
	 console.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) //jack
     console.log('obj2',obj2.name) //rose
12345678

对于数组的浅拷贝

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) //rose
        console.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) //rose
        console.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) //tennise
        console.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 = 100
   console.log(obj)
   console.log(obj2)

3.借助第三方库lodash

// 安装lodash
        npm 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) //jack
        console.log('obj2',obj2.name) //rose

标签:obj1,console,name,区别,对象,原理,拷贝,log
From: https://www.cnblogs.com/javaxubo/p/17498282.html

相关文章

  • android:layout_gravity 和 android:gravity 的区别
    gravity这个英文单词是重心的意思,在这里就表示停靠位置的意思。android:layout_gravity和android:gravity的区别从名字上可以看到,android:gravity是对元素本身说的,元素本身的文本显示在什么地方靠着换个属性设置,不过不设置默认是在左侧的。android:layout_gravity是相对与它的......
  • xxx.opt-1.pyc、xxx.opt-2.pyc和xxx.pyc的区别
    在安装python后,我们会在它的安装目录下见到大量的类似xxx.opt-1.pyc、xxx.opt-2.pyc和xxx.pyc这样的文件。比如以下这样,那么它们之前有什么区别呢?expatreader.cpython-39.opt-1.pycexpatreader.cpython-39.opt-2.pycexpatreader.cpython-39.pyc这三个文件是Python解释器......
  • Gradle里那些神奇操作的原理
    目前国内对Android领域的探索已经越来越深,不少技术领域如插件化、热修复、模块化、构建系统等都对Gradle有迫切的需求,不懂Gradle将无法完成上述事情。所以Gradle必须要学习。Gradle里的几乎任何东西都是基于这两个基础概念:taskproject掌握了这两个,你就掌握了一大半的Gradle知识......
  • Service内核原理(二):自带工程线的IntentService
    为什么要使用IntentService:IntentServiceisabaseclassfor{@linkService}sthathandleasynchronous*requests(expressedas{@linkIntent}s)ondemand.Clientssendrequests*through{@linkandroid.content.Context#startService(Intent)}calls;the*serviceis......
  • launch原理解析
    前言本章就从Continuation入手来探究一下launch启动协程的原理。正文这里我们又回到了Continuation.kt这个文件,因为这是协程框架的基础元素,上一篇文章我们介绍了创建挂起函数的俩个高阶函数就是这个类中的基础层API。除此之外,在这个类,还有启动协程的基础API。协程启动的基础API在前......
  • 【flutter 起步走】flutter共享数据利器,InheritedWidget原理探秘
    知其然,也要知其所以然。最近的搬砖工作中,开发ui页面都是使用flutter,android原生只沦为了后台逻辑处理的后盾。在搬砖过程中,往往只要知道怎么用,便能搭起小房子,而要建的恢弘又大气,还是少不了对于原理的学习。在接触flutter中,Widget是我们接触最多的类。我们对于各种界面的搭建用的就......
  • postgresql SQL 优化 -- 理论与原理
    这里写的是一个系列,关于POSTGRESQLSQL优化的问题,这篇是这个系列的第二篇,第一篇可以在文字的末尾的连接中找到,之前有同学提出,希望有一个历史文字的连接。这期就进入正题,一个SQL语句撰写出来是怎么开始工作的,也就是查询的过程queryprocessing ,这里从几个步骤入手1  一个SQL......
  • initWithNibName 和 loadNibNamed 的区别
    UIViewControllerinitWithNibName这时候是延迟加载主要代码如下:ShowViewController*showMessage=[[ShowViewControlleralloc]initWithNibName:@"ShowViewController"bundle:nil];self.showViewControllerrelease];这时候是延迟加载,这个View上的控件是nil的self.showVie......
  • Objective C中NULL、Nil、nil、NSNull 的区别
    NULLValuesThesemacrosdefinenullvaluesforclassesandinstances.NULL可以用在C语言的各种指针上#define__DARWIN_NULL#define__DARWIN_NULLConstantsc特有的,例子:int*pointerToInt=NULL;char*pointerToChar=NULL;structTreeNode*roo......
  • Kotlin协程:Flow基础原理
    本文分析示例代码如下:launch(Dispatchers.Main){flow{emit(1)emit(2)}.collect{delay(1000)withContext(Dispatchers.IO){Log.d("liduo","$it")}Log.d("liduo",&......