JavaScript中,型別有兩大分類:Primitive Type 和 Object Type。
除此之外,還有另一種區分:by value 與 by reference。
我們先分析Primitive Type:
let a = 1;
let b = a;
console.log(a); //1
console.log(b); //1
宣告變數a,設定值為1,代表這個變數在記憶體有個位置,假設為0x001,裡面的值是1,所以我們需要取出這個值,只要輸入變數a,就可以從這個記憶體位置取出值1。
b=a,表示宣告變數b,記憶體中一樣有b的位置,假設為0x002,並且把a所代表記憶體位置的值複製(copy)給b,所以目前記憶體中有a(0x001)跟b(0x002),不同的位址,以及都有同樣的值。這個方式就叫做(by value)
Object Type:
let obj1 = {
prop: 'obj1'
}
let obj2 = obj1;
console.log(obj1.prop); //obj1
console.log(obj2.prop); //obj1
宣告一個物件,它有屬性prop,值為obj1,這個變數(obj1)所保存的值不是物件本身,而是一個記憶體
位址,假設為0x001,這個位址會指向另一個地方(同樣在記憶體),真正保存物件的地方。
obj2 = obj1,會將變數obj1的值(0x001),傳給obj2,意味著obj2與obj1是指向同一個位址(0x001),或者可以說指向同一個物件,當我們要取出它們值的時候,所指向的都是同一個記憶體位址,這種方式叫(by reference)。
上面的例子雖然兩個變數輸出的結果都一樣,但實際上卻有很大的差異。
先以Primitive Type來說:
let a = 1;
let b = a;
b = 100;
console.log(`a=${a}`); //a=1
console.log(`b=${b}`); //b=100
當宣告b設定為a的值(1)之後,又再次把b的值設為100,結果a跟b的值會不一樣。
因為a跟b是不同的記憶體位址,改變其中一個,不會影響到另一個。
Object Type的範例:
let obj1 = {
prop: 'obj1'
}
let obj2 = obj1;
obj2.prop = 'obj2';
console.log(obj1.prop); //obj2
console.log(obj2.prop); //obj2
當我們改變obj2的屬性值,連帶也影響到obj1,這是為什麼?
因為剛剛說過了,obj2與obj1是指向同一個位址,意味著它們所參考(reference)的是同一個物件。
再看看其他的範例:
let a = 1;
let b = a;
let c = 1;
console.log(`a === b : ${a===b}`); //a === b : true
console.log(`a === c : ${a===c}`); //a === c : true
變數a、b、c的值都是1,「===」運算子比較的是變數的值,所以結果都為true。
Object Type的範例:
let obj1 = {
prop: 'obj1'
};
let obj2 = obj1;
let obj3 = {
prop: 'obj1'
};
console.log(`obj1===obj2:${obj1===obj2}`); //obj1===obj2:true
console.log(`obj1===obj3:${obj1===obj3}`); //obj1===obj3:false
obj1===obj2為true,這沒啥問題,obj1===obj3的結果為false,為什麼?
明明obj1跟obj3它們的內容都一樣阿。
當我們宣告obj3並設定它的屬性值時,會設立一個新的記憶體位置保存obj3的物件。
這時,obj1與obj3所指向的,是不同物件,但物件的內容是相同的。
obj1===obj3所比較的是變數的值,也就是記憶體位址,因為指向不同的物件,位址自然不同,所以結果為false。
了解by value和by reference後,
我們來看看傳值(call by value)與傳址(call by reference)。
先看看傳值:
let a = 1;
function addValue(x) {
x++;
return x;
}
console.log(addValue(a)); //2
console.log(a); //1
函式addValue的參數(x),被視為區域變數,它的作用域僅限於函式內,a的型別為Primitive Type,呼叫函式,以a做為引數傳入,實際上,是把值複製(copy)給區域變數x,但因為x為區域變數,不管它如何運算,都不會影響到位在全域環境的a。
陣列也是物件的一種,這次我們改用陣列示範傳址:
let ary = [1, 2, 3];
function addElement(value) {
value.push(4);
return value;
}
console.log(addElement(ary)); //[1, 2, 3, 4]
console.log(ary); //[1, 2, 3, 4]
結果函式改變了原陣列的值,既然陣列是物件,那表示變數的值並不是陣列,而是位址,當ary作為引數傳入函式時,它把位址複製給value,雖然value是區域變數,但它所指向的陣列是跟ary同一個,所以函式執行陣列操作後,亦會影響全域變數所指向的物件。