好程式設計師web前端教程分享引用型別與基本型別
好程式設計師web前端教程分享引用型別與基本型別,本文將從以下六個方面講解引用型別和基本型別
1. 概念
2. 記憶體圖
3. 引用型別和基本型別作為函式的引數體現的區別
4. 引用型別的優點:
5. 引用型別的賦值(對比基本型別)
6. 淺拷貝和深拷貝
以下為詳細內容:
1. 概念:
基本型別也叫簡單型別,儲存的資料是單一的,如:學生的個數就是一個數字而已;引用型別也叫複雜型別,儲存的資料是複雜的,如:學生,包括學號,姓名,性別,年齡等等很多資訊。從記憶體(大家如果不懂記憶體,請查閱相關資料)的角度來說:基本型別只佔用一塊記憶體區域;引用型別佔用兩塊記憶體區域。即定義基本型別的變數時,在記憶體中只申請一塊空間,變數的值直接存放在該空間;定義引用型別的變數時(容易理解的是,我門看到new運算子,一般就是定義引用型別的變數),在記憶體中申請兩塊空間,第一塊空間儲存的是第二塊空間的地址,第二塊空間儲存的是真正的資料;第一塊空間叫作第二塊空間的引用(地址),所以叫作引用型別。
javaScript中的基本型別包括:數字(Number),字串(String),布林(Boolean),Null,Undefined五種;
javascript的引用型別是:Object。而Array,Date是屬於Obejct型別。
2. 記憶體圖:
如下程式碼(都是定義了兩個區域性變數):
function demoFun(){
var num = 20;//定義了一個基本型別的變數。
var arr = new Array(12,23,34);//定義了一個引用型別的變數
}
以上兩行程式碼的記憶體圖:
可以看到,num變數只佔用了一塊記憶體區域;arr變數佔用了兩塊記憶體區域,arr變數在棧區(不懂棧區的人,先不要想太多)申請了一塊記憶體區域,儲存著地址,儲存的地址是堆區的地址。而堆區中真正才儲存著資料,所以說,arr變數佔用了兩塊記憶體區域。這樣看來,引用型別的變數好像還佔用記憶體多了。哈哈,不要著急,後面瞭解了引用型別的優點後,你就會覺得這是問題了。
當我們讀取num變數的值時,直接就能讀到,但是當我們要讀取arr裡的值時,先找到arr中的地址,然後根據地址再找到對應的資料。
引用型別,類似於windows作業系統中的快捷方式。快捷方式就是一個地址,真正的內容是快捷方式所指向的路徑的內容。如:我們把d:\t.txt檔案建立一個快捷方式放在桌面上,那麼,桌面上的快捷方式會佔用桌面的空間,而d:\t.txt會佔用d盤的空間,所以,佔用了兩塊空間。
基本型別就相當於檔案。
引用型別,類似於我們在入學報名填寫報名表時,填寫家庭地址,這個家庭地址就相當於第一塊空間,真正你家(第二塊記憶體空間)不在報名表上。學校要找你家,先在報名表上找到你家的地址,然後根據地址,才能找到你家去。
3. 引用型別的優點:
引用型別作為函式的引數時,優點特別明顯,第一,形參傳遞給實參時,只需要傳遞地址,而不需要搬動大量的資料(節約了記憶體開銷);第二,形參對應的資料改變時,實參對應的資料也在改變(很多時候,我們希望這樣)。
如以下程式碼:
先定義函式(氣泡排序)
function bubble(arr){
for(var i=0;i
for(var j=0;j
if(arr[j]>arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
當呼叫氣泡排序時,
var arr1 = [250,2,290,35,12,99];
bubble(arr1);
看看記憶體以上程式碼執行時的,記憶體變化:
圖中,當執行,①對應的程式碼(var arr1 = [250,2,290,35,12,99];)時,記憶體中會產生①對應的變化,即在棧中申請一塊記憶體區域,起名為arr1,在堆區中申請記憶體空間放置250,2,290,35,12,99,並把堆區中的記憶體的地址賦給arr1的記憶體中;當執行②對應的程式碼bubble(arr1)時,呼叫函式。這時候會定義形參arr(記憶體中③對應的變化),即在棧中申請一塊記憶體區域,起名為arr,並把arr1儲存的地址賦給了arr(記憶體中②表示的賦值),這樣,形參arr和實參arr1就指向了同一塊記憶體區域。陣列中的值250,2,290,35,12,99在記憶體中只有一份。即,不用把陣列中每個元素的值再複製一份,節約了記憶體。如果對記憶體圖看懂了,那麼,當形參arr對應的資料順序改變了,實參arr1對應的資料順序也就改變了。即,實現了形引數據改變時,實引數據也改變了。所以,bubble函式不需要返回值,依然可以達到排序的目的。可以執行我示例中的程式碼,看看是不是達到了排序的效果。
補充,基本型別作為函式引數的記憶體變化:
記憶體圖:
4. 引用型別變數的賦值:
引用型別變數賦值時,賦的是地址。即兩個引用型別變數裡儲存的是同一塊地址,也就是說,兩個引用型別變數都指向同一塊記憶體區域。所以,兩個引用型別變數對應的資料時一樣的。
再如:
var person1 = {
name:"張三",
sex:"男",
age:12
};
var person2 = person1;
person2.name="張四"; //這句話會改變person1和person2的name。說明person1和person2的name佔用的是同一塊記憶體。
alert(person1.name+","+person1.sex+","+person1.age);
alert(person2.name+","+person2.sex+","+person2.age);
基本型別變數賦值時的記憶體變化。
5. 淺拷貝和深拷貝
先說物件的複製,上面說了,引用型別(物件)的賦值,只是賦的地址,那麼要真正複製一份新的物件(即克隆)時,又該怎麼辦。
var person1 = {
name:"張三",
sex:"男",
age:12
};
var person2={};
for(var key in person1){
person2[key] = person1[key];
}
但是,當一個物件的屬性又是一個引用型別時,會出現淺拷貝和深拷貝的問題。用一個自定義的object型別來說明問題。
如:
var person1 = {
name:"張三",
sex:"男",
age:12,
address:{
country:"陝西",
city:"渭南"
}
};
//物件person1的address又是個物件,即,要對person1做真正的克隆,需要把address中的每個屬性也進行克隆。
var person2={};
for(var key in person1){
person2[key] = person1[key];
}
person2.name="張四"; //不會改變掉person1的name屬性。
person2.address.country="北京";//會改變掉person1的address.country
大家注意看,person1和person2的name屬性各有各的空間,但是person1.address.country和person2.address.country是同一塊空間。所以,改變person2.address.country的值時,person1.address.country的值也會改變。這就說明拷貝(克隆)的不到位,這種拷貝叫作淺拷貝,而進一步把person1.address.country和person1.address.name也拷貝(克隆)了,就是深拷貝。要做到深拷貝,就需要對每個屬性的型別進行判斷,如果是引用型別,就再迴圈進行拷貝(