聊一聊web前端那些事兒,關於深複製和淺複製

千鋒HTML5學院發表於2019-09-10

深複製和淺複製是經常在面試中會出現的,主要考察你對基本型別和引用型別的理解深度。我在無數次的面試中,應聘者還沒有一個人能把這個問題回答情況,包括很多機構的培訓老師。這篇文章會讓你把基本型別和引用型別的區別搞得清清楚楚,搞清楚這兩者的區別,你對任何程式語言的都不怕,因為,這不是js一門語言,是任何程式語言中都需要掌握的知識,而且,在任何程式語言中,兩者都是一樣的。

深複製和淺複製主要是針對物件的屬性是物件(引用型別)

一、基本型別和引用型別的區別

1、先了解記憶體

任何程式語言的記憶體分割槽幾乎都是一樣的

記憶體是儲存資料的,不同型別的資料要儲存在不同的區域,即分類存放,不同的區域作用和功能也不一樣。就像你家裡的衣櫃一樣,也分了不同的區域:如掛西裝的區域,放襪子的區域等等,我相信每個人都會把這兩個東西放在不同的區域。要不然,當你西裝革履地參加一個高檔的宴會,手塞在褲兜裡,掏出來一隻臭襪子,是不是很尷尬!!!哈哈!!!

以下為記憶體的分割槽圖。記憶體分為四個區域:棧區(堆疊),堆區,全域性靜態區,只讀區(常量區和程式碼區)。

https://blog.csdn.net/jiang7701037/article/details/98728249

2、基本型別和引用型別在記憶體上儲存的區別

現在只看棧區和堆區,不管其它區域,也假定只是區域性變數。

聊一聊web前端那些事兒,關於深複製和淺複製

以上函式testf在呼叫時,

       1)、 定義區域性變數  age,由於age是區域性變數,所以在棧中申請記憶體空間,起名為age,又由於給age賦的值250是基本型別,所以,值直接儲存在棧中。

        2)、定義區域性變數arr,由於arr是區域性變數,所以在棧中申請空間,但是arr的記憶體中儲存的是什麼?由於給arr賦的值不是基本型別,而是引用型別(new出來的),所以,先在堆中申請空間存放資料  12,23,34,。再把堆區的地址賦給arr。

3、到底什麼是基本型別和引用型別

1)、基本型別:就是值型別,即在變數所對應的記憶體區域儲存的是值,如:上面的age變數所對應的記憶體儲存的就是值250.

2)、引用型別:就是地址型別。

  何為地址:地址就是編號,要地址何用,就是為了容易找到。每個人的家裡為什麼要有一個唯一的地址,就是在郵寄時,能夠找到你家。

比如:我們最早的超市存包的格子,每個格子都有個編號,你存包時,服務員會把你的東西放在某個格子裡,再把這個格子的編號給你(一個牌子)。你購物完畢取包時,直接給服務員你的牌子(有編號),服務員根據你的編號就會找到你的包。這個編號就是格子的地址。記憶體也是一樣的,每個記憶體都有一個編號,方便cpu查詢。要不然,浩瀚的記憶體海洋,cpu要找到資料靠啥找。

以上的變數arr就是引用型別,arr所對應的記憶體中儲存著地址,真正的資料是在地址對應的記憶體區域裡,就像,你填寫簡歷時,會在簡歷的那張紙上寫上你家的地址。簡歷上寫你家地址的地方就相當於arr。而你家是根據這個地址可以找到的。簡歷上寫你家地址的地方就相當於引用著你家(可以想象一根無形的線牽引著你家,在簡歷上的這根無形的線,順藤摸瓜就能找到你家)。所以叫做引用型別。

二、基本型別和引用型別在賦值時記憶體的變化

你可以認為,賦值就是在複製。

1、基本型別:

聊一聊web前端那些事兒,關於深複製和淺複製

2、引用型別:

聊一聊web前端那些事兒,關於深複製和淺複製

如果給arr[0]賦值的話,arr1[0]的值也會發生變化,因為,arr和arr1儲存著相同的地址,它門兩個引用的資料是共享的。就像你在很多地方(簡歷的那張紙,戶口本上的那張紙)會寫上你的家庭地址。這麼多張紙都引用著你家。根據一張紙上找到你家,給你家放上一百萬的現金(資料改變了,相當於arr[0]=10),再根據另外一張紙的地址也找到了你家,你發現你一百萬在(不要給我說被人拿了)

如果在上面的基礎上增加一句程式碼:arr[0]=10;那麼記憶體將會有如下變化:

聊一聊web前端那些事兒,關於深複製和淺複製

三、基本型別和引用型別作為函式引數的區別(這個可以不看)

1、基本型別作為函式的引數

聊一聊web前端那些事兒,關於深複製和淺複製

2、引用型別作為函式的引數:

聊一聊web前端那些事兒,關於深複製和淺複製

四、深複製和淺複製:

  終於說到了深複製和淺複製。

其實在第二點已經說到了複製,所謂複製,就是賦值。把一個變數賦給另外一個變數,就是把變數的內容進行複製。把一個物件的值賦給另外一個物件,就是把一個物件複製一份。

1、基本類沒有問題,

因為,基本型別賦值時,賦的是資料(所以,不存在深複製和淺複製的問題)。

      如:

   Var x  = 100;

   Var  y = x; //此時x和y都是100;

  如果要改變y的值,x的值不會改變。

2、引用型別有問題

因為,引用型別賦值時,賦的值地址(就是引用型別變數在記憶體中儲存的內容),強烈建議把前面的第二點(基本型別和引用型別在賦值時記憶體的變化)多看幾遍,以保證理解深刻。這樣,一勞永逸,以後在碰到任何跟引用型別有關的話題(如:繼承時,父類的屬性是引用型別)都沒有問題。

     如:

var  arr1 = new Array(12,23,34)

Var  arr2 = arr1;//這就是一個最簡單的淺複製

如果要改變arr2所引用的資料:arr2[0]=100時,那麼arr1[0]的值也是100。

       原因就是  arr1和arr2引用了同一塊記憶體區域(以上的第二點中有體現)。  

這是最簡單的淺複製,因為,只是把arr1的地址複製的一份給了arr2,並沒有把arr1的資料複製一份。所以,複製的深度不夠

3、用json物件的方式(也是引用型別)來演示淺複製和深複製

1)、定義一個json物件(物件的屬性也是物件)

00001.

var p  = {

"id":"007",

"name":"劉德華",

"books":new Array("三國演義","紅樓夢","水滸傳")//這是引用型別

}

00002.

聊一聊web前端那些事兒,關於深複製和淺複製

2)、把該物件p進行復制一份

· (一)淺複製

00001.

var p2  = {};

for(let key  in p){

p2[key]  = p[key];

}

p2.books[0]  ="四國";

console.log(p2);

console.log(p);

00002.

在控制檯中列印的結果(p和p2的books[0]都變成了“四國”):

聊一聊web前端那些事兒,關於深複製和淺複製

記憶體:

聊一聊web前端那些事兒,關於深複製和淺複製

(二) 深複製(初步)

var  p2 = {};

for(let  key in p){

if(typeof  p[key]=='object'){

p2[key]=[];//因為,我上面寫的是陣列,所以,暫時賦值一個空陣列.

for(let  i in p[key]){

p2[key][i]  = p[key][i]

}

}else{

p2[key]  = p[key];

}

}

p2.books[0]  ="四國";

console.log(p2);

console.log(p);

在控制檯中列印的結果(只有p2的books[0]變成了“四國”)

聊一聊web前端那些事兒,關於深複製和淺複製

記憶體:

聊一聊web前端那些事兒,關於深複製和淺複製

(三)深複製(最終)

3.1、深複製_如果屬性都是json物件,那麼用遞迴的方式

//如果物件的屬性是物件(引用型別),屬性的屬性也是引用型別,即層層巢狀很多.怎麼辦,只能遞迴

//如下物件,要複製:

00001.

var  p = {

"id":"007",

"name":"劉德華",

"wife":{

"id":"008",

"name":"劉德的妻子",

"address":{

"city":"北京",

"area":"海淀區"

}

}

}

//寫函式

function  copyObj(obj){

let  newObj={};

for(let  key in obj){

if(typeof  obj[key] =='object'){//如:key是wife,引用型別,那就遞迴

newObj[key]  = copyObj(obj[key])

}else{//基本型別,直接賦值

newObj[key]  = obj[key];

}

}

return  newObj;

}

let  pNew = copyObj(p);

pNew.wife.name="張三瘋";

pNew.wife.address.city  = "香港";

console.log(pNew);

console.log(p);

聊一聊web前端那些事兒,關於深複製和淺複製

3.2、深複製_如果屬性是陣列等非鍵值對的物件

      就得單獨處理:要麼給陣列增加一個自我複製的函式(建議這樣做),要麼單獨判斷。

```

//給陣列物件增加一個方法,用來複制自己

Array.prototype.copyself  = function(){

let  arr = new Array();

for(let  i in this){

arr[i]   = this[i]

}

return  arr;

}

var  p = {

"id":"007",

"name":"劉德華",

"books":new  Array("三國演義","紅樓夢","水滸傳")//這是引用型別

}

function  copyObj(obj){

let  newObj={};

for(let  key in obj){

if(typeof  obj[key] =='object'){//如:key是wife,引用型別,那就遞迴

newObj[key]  = obj[key].copyself();

}else{//基本型別,直接賦值

newObj[key]  = obj[key];

}

}

return  newObj;

}

var  pNew = copyObj(p);

pNew.books[0]  = "四國";

console.log(pNew);

console.log(p);  

```

聊一聊web前端那些事兒,關於深複製和淺複製


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69917019/viewspace-2656660/,如需轉載,請註明出處,否則將追究法律責任。

相關文章