深拷貝和淺拷貝的區別和與原理

一、基本類型和引用類型

https://blog.csdn.net/qq_34645412/article/details/104840390

二、淺拷貝和深拷貝

如圖所示:

obj2是對obj1的淺拷貝,obj2新建了一個對象,但是obj2對象複製的是obj1的指針,也就是obj1的堆內存地址,而不是複製對象本身。obj1和obj2是共用了內存地址的。

obj3是對obj1的深拷貝,obj3和obj1不共享內存

因此:

淺拷貝只複製指向某個對象的指針,而不復制對象本身,相當於是新建了一個對象,該對象複製了原對象的指針,新舊對象還是共用一個內存塊,

深拷貝是新建一個一模一樣的對象,該對象與原對象共享內存,修改新對象也不會影響原對象

三、賦值與淺拷貝

1.賦值

當我們把一個對象賦值給一個變量的時候,賦值的其實是該對象的棧內存地址而不是堆內存數據,(此處看基本類型和引用類型,對象屬於引用類型,值分爲棧內存的地址和堆內存中的數據)。也就是賦值前的對象和賦值後的對象兩個對象共用一個存儲空間(賦值的是棧內存地址,而該地址指向了同一個堆內存空間),所以,無論哪個對象發生改變,改變的都是同一個堆堆內存空間。因此,無論修改哪個對象對另一個對象都是有影響的

 var obj1 ={
            name:'jack',
            age:25,
            hobby:['tennis','swiming','basketball']
        }
        var obj2 = obj1
        obj2.name = 'rose'
        obj2.hobby[1] = 'read'
        console.log('obj1.name',obj1.name) 
        console.log('obj2.name',obj2.name)
        console.log('obj2.hobby',obj2.hobby)
        console.log('obj1.hobby',obj1.hobby)

結果:

從結果可以看出對賦值後的對象obj2進行改變,原對象obj1的值也進行了改變,原因就是賦值後的對象obj2賦值的是原對象obj1的棧內存地址,他們指向的是同一個堆內存數據,所以對賦值後的對象obj2對數據進行操作會改變了公共的堆內存中的數據,所以原對象的值也改變了。反之亦然,對原對象進行改變,obj2的值也會發生改變

 var obj1 ={
            name:'jack',
            age:25,
            hobby:['tennis','swiming','basketball']
        }
        var obj2 = obj1
        obj1.name = 'rose'
        obj1.hobby[1] = 'read'
        console.log('obj1.name',obj1.name)
        console.log('obj2.name',obj2.name)
        console.log('obj2.hobby',obj2.hobby)
        console.log('obj1.hobby',obj1.hobby)

結果同上

2.淺拷貝

淺拷貝是按位拷貝對象,它會創建一個新對象,這個對象有着原對象屬性值的一份精準拷貝,如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。即默認拷貝構造函數只是對對象進行淺拷貝複製(逐個成員依次拷貝),即只複製對象空間而不復制資源。

有點抽象,來看個例子,該例子也是手寫淺拷貝的方法

var obj1 ={
            name:'jack',
            age:25,
            hobby:['tennis','swiming','basketball']
        }
       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 = 'rose'
        obj3.hobby[1] = 'read'
        console.log('obj1',obj1)
        console.log('obj3',obj3)

結果:

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

對於數組的淺拷貝

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章