
為了讓讀者更好的理解深淺拷貝,在講深淺拷貝之前要引入基本數(shù)據(jù)類型 , 引用數(shù)據(jù)類型 和 數(shù)據(jù)儲(chǔ)存(棧和堆)這幾個(gè)概念,如果已經(jīng)理解,可直接跳過(guò)這一part。
JS數(shù)據(jù)類型
Q:前端面試常問(wèn),JS的基本數(shù)據(jù)類型有哪些呀?
A:JS數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,詳細(xì)分類如下:

Q:基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的儲(chǔ)存方式有什么不同?
A:
- 基本數(shù)據(jù)類型:變量名和值都儲(chǔ)存在棧內(nèi)存中,例如:
var num=10;
num變量在內(nèi)存中儲(chǔ)存如下:

- 引用數(shù)據(jù)類型:變量名儲(chǔ)存在棧內(nèi)存中,值儲(chǔ)存在堆內(nèi)存中,但是堆內(nèi)存中會(huì)提供一個(gè)引用地址指向堆內(nèi)存中的值,而這個(gè)地址是儲(chǔ)存在棧內(nèi)存中的,例如:
var arr=[1,2,3,4,5];
arr變量在內(nèi)存中的儲(chǔ)存如下:

對(duì)這幾個(gè)概念有了初步了解之后,接下來(lái)正式開(kāi)始講深淺拷貝。
什么是淺拷貝和深拷貝
在講兩者概念之前我們先看一個(gè)需求:現(xiàn)在有一個(gè)對(duì)象A,需求是將A拷貝一份到B對(duì)象當(dāng)中?
淺拷貝
當(dāng)B拷貝了A的數(shù)據(jù),且當(dāng)B的改變會(huì)導(dǎo)致A的改變時(shí),此時(shí)叫B淺拷貝了A,例如:
//淺拷貝 var A={ name:"martin", data:{num:10} } var B={} var B=A; B.name="lucy"; console.log(A.name); //lucy
A直接賦值給B后,B中name屬性的改變導(dǎo)致了A中name屬性也發(fā)生了變化。
其實(shí)是因?yàn)檫@種賦值方式只是將A的堆內(nèi)存地址賦值給了B,A和B儲(chǔ)存的是同一個(gè)地址,指向的是同一個(gè)內(nèi)容,因此B的改變當(dāng)然會(huì)引起A的改變。
淺拷貝的方式
直接賦值
第一種方式就是上面所寫代碼中的將對(duì)象地址直接進(jìn)行賦值。
var A={ name:"martin", data:{num:10} }; var B={}; B=A; B.name="lucy"; console.log(A.name); //"lucy",A中name屬性已改變
Object.assign(target,source)
這是ES6中新增的對(duì)象方法,對(duì)它不了解的見(jiàn)ES6對(duì)象新增方法,它可以實(shí)現(xiàn)第一層的“深拷貝”,但無(wú)法實(shí)現(xiàn)多層的深拷貝。
以當(dāng)前A對(duì)象進(jìn)行說(shuō)明
第一層“深拷貝”:就是對(duì)于A對(duì)象下所有的屬性和方法都進(jìn)行了深拷貝,但是當(dāng)A對(duì)象下的屬性如data是對(duì)象時(shí),它拷貝的是地址,也就是淺拷貝,這種拷貝方式還是屬于淺拷貝。
多層深拷貝:能將A對(duì)象下所有的屬性,及時(shí)屬性是對(duì)象,也能夠深拷貝出來(lái),讓A和B相互獨(dú)立,這種叫才叫深拷貝。
var A={ name:"martin", data:{num:10}, say:function(){ console.log("hello world") } } var B={} Object.assign(B,A); //將A拷貝到B B.name="lucy"; console.log(A.name); //martin,發(fā)現(xiàn)A中name并沒(méi)有改變 B.data.num=5; console.log(A.data.num); //5,發(fā)現(xiàn)A中data的num屬性改變了,說(shuō)明data對(duì)象沒(méi)有被深拷貝
淺拷貝總結(jié)
直接賦值:這種方式實(shí)現(xiàn)的就是純粹的淺拷貝,B的任何變化都會(huì)反映在A上。
Object.assign():這種方式實(shí)現(xiàn)的實(shí)現(xiàn)的是單層“深拷貝”,但不是意義上的深拷貝,對(duì)深層還是實(shí)行的淺拷貝。
深拷貝
當(dāng)B拷貝了A的數(shù)據(jù),且當(dāng)B的改變不會(huì)導(dǎo)致A的改變時(shí),此時(shí)叫B深拷貝了A,例如:
//深拷貝 var A={ name:"martin", data:{num:10}, say:function(){ console.log("hello world") } } //開(kāi)辟了一個(gè)新的堆內(nèi)存地址,假設(shè)為placaA var B={}; //又開(kāi)辟了一個(gè)新的堆內(nèi)存地址,假設(shè)為placeB B=JSON.parse(JSON.stringfy(A)); B.name="lucy"; console.log(A.name); //martin
通過(guò)JSON對(duì)象方法實(shí)現(xiàn)對(duì)象的深拷貝,我們可以看到其中B.name值的改變并沒(méi)有影響A.name的值,因?yàn)锳和B分別指向不同的堆內(nèi)存地址,因此兩者互不影響。
深拷貝的方式
理解了深淺拷貝,接下來(lái)說(shuō)一下深拷貝的幾種方式。
首先假設(shè)一個(gè)已知的對(duì)象A,然后需要把A深拷貝到B。
var A={ name:"martin", data:{num:10}, say:function () { console.log("say"); } }; var B={};
遞歸賦值
function deepCopy(A,B) { for(item in A){ if(typeof item=="object"){ deepCopy(item,B[item]); }else{ B[item]=A[item]; } } } deepCopy(A,B); B.data.num=5; console.log(A.data.num); //10,A中屬性值并沒(méi)有改變,說(shuō)明是深拷貝
通過(guò)這種方式能實(shí)現(xiàn)深層拷貝,而且能自由控制拷貝是如何進(jìn)行的,如:當(dāng)B中有和A同名的屬性,要不要重新賦值?這些都可以進(jìn)行控制,但是代碼相對(duì)復(fù)雜一些。
JSON.parse()和JSON.stringify
var B=JSON.parse(JSON.stringify(A)); B.data.num=5; console.log(A.data.num); //10,A中屬性值并沒(méi)有改變,說(shuō)明是深拷貝
用這種方式實(shí)現(xiàn)深拷貝的時(shí)候要 注意 , 函數(shù)是無(wú)法進(jìn)行拷貝的,會(huì)被丟失 ,上述代碼中B也并沒(méi)有拷貝出A中的say函數(shù),這和JSON.stringify方法的規(guī)則有關(guān)系,它在序列化的時(shí)候會(huì)直接忽略函數(shù),因此最后A中的say函數(shù)沒(méi)有被拷貝到B,關(guān)于JSON.stringify序列化的具體規(guī)則見(jiàn)JSON.stringify指南。
深拷貝總結(jié)
遞歸:使用遞歸進(jìn)行深拷貝時(shí)比較靈活,但是代碼較為復(fù)雜;
JSON對(duì)象:JSON對(duì)象方法實(shí)現(xiàn)深拷貝時(shí)比較簡(jiǎn)單,但是當(dāng)拷貝對(duì)象包含方法時(shí),方法會(huì)被丟失;
因此使用者可按自身的使用場(chǎng)景來(lái)選擇拷貝方式