一、基本類型和引用類型
基本類型:string,number,boolean,null,undefiend,symbol
引用類型:Function,Array,Object
二、淺拷貝和深拷貝
如圖所示:
obj2是對obj1的淺拷貝,obj2新建了一個對象,但是obj2對象復制的是obj1的指針,也就是obj1的堆內存地址,而不是復制對象本身。obj1和obj2是共用了內存地址的。
obj3是對obj1的深拷貝,obj3和obj1不共享內存
因此:
淺拷貝只復制指向某個對象的指針,而不復制對象本身,相當於是新建了一個對象,該對象復制了原對象的指針,新舊對象還是共用一個內存塊,
深拷貝是新建一個一模一樣的對象,該對象與原對象共享內存,修改新對象也不會影響原對象
三、賦值與淺拷貝
1.賦值
當我們把一個對象賦值給一個變量的時候,賦值的其實是該對象的棧內存地址而不是堆內存數據,(此處看基本類型和引用類型,對象屬於引用類型,值分為棧內存的地址和堆內存中的數據)。也就是賦值前的對象和賦值后的對象兩個對象共用一個存儲空間(賦值的是棧內存地址,而該地址指向了同一個堆內存空間),所以,無論哪個對象發生改變,改變的都是同一個堆堆內存空間。因此,無論修改哪個對象對另一個對象都是有影響的
結果:
從結果可以看出對賦值后的對象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