1、棧(stack)和堆(heap)
stack為自動分配的內存空間,它由系統自動釋放;而heap則是動態分配的內存,大小也不一定會自動釋放
2、數據類型
JS分兩種數據類型:
基本數據類型:Number、String、Boolean、Null、 Undefined、Symbol(ES6),這些類型可以直接操作保存在變量中的實際值。
引用數據類型:Object(在JS中除了基本數據類型以外的都是對象,數據是對象,函數是對象,正則表達式是對象)
3、基本數據類型(存放在棧中)
基本數據類型是指存放在棧中的簡單數據段,數據大小確定,內存空間大小可以分配,它們是直接按值存放的,所以可以直接按值訪問
var
a = 10;
var
b = a;
b = 20;
console.log(a);
// 10值
console.log(b);
// 20值
|
下圖演示了這種基本數據類型賦值的過程:
4、引用數據類型(存放在堆內存中的對象,每個空間大小不一樣,要根據情況進行特定的配置)
引用類型是存放在堆內存中的對象,變量其實是保存的在棧內存中的一個指針(保存的是堆內存中的引用地址),這個指針指向堆內存。
引用類型數據在棧內存中保存的實際上是對象在堆內存中的引用地址。通過這個引用地址可以快速查找到保存中堆內存中的對象
var obj1 = new Object(); var obj2 = obj1; obj2.name = "我有名字了"; console.log(obj1.name); // 我有名字了
說明這兩個引用數據類型指向了同一個堆內存對象。obj1賦值給obj2,實際上這個堆內存對象在棧內存的引用地址復制了一份給了obj2,但是實際上他們共同指向了同一個堆內存對象,所以修改obj2其實就是修改那個對象,所以通過obj1訪問也能訪問的到。
1
2
3
4
5
6
7
8
9
10
|
var
a = [1,2,3,4,5];
var
b = a;
//傳址 ,對象中傳給變量的數據是引用類型的,會存儲在堆中;
var
c = a[0];
//傳值,把對象中的屬性/數組中的數組項賦值給變量,這時變量C是基本數據類型,存儲在棧內存中;改變棧中的數據不會影響堆中的數據
alert(b);
//1,2,3,4,5
alert(c);
//1
//改變數值
b[4] = 6;
c = 7;
alert(a[4]);
//6
alert(a[0]);
//1
|
從上面我們可以得知,當我改變b中的數據時,a中數據也發生了變化;但是當我改變c的數據值時,a卻沒有發生改變。
這就是傳值與傳址的區別。因為a是數組,屬於引用類型,所以它賦予給b的時候傳的是棧中的地址(相當於新建了一個不同名“指針”),而不是堆內存中的對象。而c僅僅是從a堆內存中獲取的一個數據值,並保存在棧中。所以b修改的時候,會根據地址回到a堆中修改,c則直接在棧中修改,並且不能指向a堆內存中。
5、淺拷貝
前面已經提到,在定義一個對象或數組時,變量存放的往往只是一個地址。當我們使用對象拷貝時,如果屬性是對象或數組時,這時候我們傳遞的也只是一個地址。因此子對象在訪問該屬性時,會根據地址回溯到父對象指向的堆內存中,即父子對象發生了關聯,兩者的屬性值會指向同一內存空間。
var a={key1:"11111"} function Copy(p){ var c ={}; for (var i in p){ c[i]=p[i] } return c; } a.key2 = ["小輝","小輝"] var b = Copy(a); b.key3 = "33333" alert(b.key1)//11111 alert(b.key3)//33333 alert(a.key3);//undefined
b.key2.push("大輝") alert(a.key2);//小輝,小輝,大輝
但是若是修改的屬性變為對象或數組時,那么父子對象之間就發生關聯,從上可知:
原因是key1的值屬於基本類型,所以拷貝的時候傳遞的就是該數據段;但是key2的值是堆內存中的對象,所以key2在拷貝的時候傳遞的是指向key2對象的地址,無論復制多少個key2,其值始終是指向父對象的key2對象的內存空間。
//ES6實現淺拷貝的方法 var a = {name:"暖風"} var b= Object.assign({},a); b.age = 18; console.log(a.age);//undefined ---------------------------------- //數組 var a = [1,2,3]; var b = a.slice(); b.push(4); b//1,2,3,4 a//1,2,3 ---------------------------------- var a = [1,2,3]; var b = a.concat(); b.push(4); b//1,2,3,4 a//1,2,3 ---------------------------------- var a = [1,2,3]; var b = [...a] b//1,2,3,4 a//1,2,3
6、深拷貝
或許以上並不是我們在實際編碼中想要的結果,我們不希望父子對象之間產生關聯,那么這時候可以用到深拷貝。既然屬性值類型是數組和或象時只會傳址,那么我們就用遞歸來解決這個問題,把父對象中所有屬於對象的屬性類型都遍歷賦給子對象即可。測試代碼如下:
var a={key1:"11111"} function Copy(p,c){ var c =c||{}; for (var i in p){ if(typeof p[i]==="object"){ c[i]=(p[i].constructor ===Array)?[]:{} Copy(p[i],c[i]); }else{ c[i]=p[i] } } return c; } a.key2 = ["小輝","小輝"] var b = {} b = Copy(a,b); b.key2.push("大輝"); b.key2//小輝,小輝,大輝 a.key2//小輝,小輝