淺談變數的傳遞方式差異與注意事項。
在變數指定的時候,一般都是 Call by value。
但如果遇到 Object 型態,基本上就會是 Call by reference。
Call by value
宣告一個變數後,記憶體會切一塊位置給此變數存放內容。
var a = 1;
// 使用記憶體位置 0x01 存放變數 a 存放 value 為 1
var b = a;
// 使用記憶體位置 0x02 存放變數 b,並且把 a 的 value 給 b
b = 90;
// 將 value 90 寫入 b 的記憶體位置 0x02
console.log(a); // 1
console.log(b); // 90
雖然使用 var b = a; 來指定 a 的 value,但是 a 跟 b 是獨立記憶體位置,所以後面再去異動 b 時,跟 a 毫無關係。
Call by reference
宣告一個物件或是陣列後,會先切一塊記憶體存放變數,在切一塊記憶體存放物件內容。
為什麼陣列也是 Call by reference ?
因為他也是 Object 的一種,從記憶體分配跟存取方式就可以推測出來。
var foo = {name: 'into', age: 18};
// 使用記憶體位置 0x01 存放變數 foo
// 使用記憶體位置 0x02 存放物件內容 {name: 'into', age: 18}
// 將 foo 記憶體位置 0x01 value 寫入記憶體指標 0x02
var bar = foo;
// 使用記憶體位置 0x03 存放變數 bar
// 將 bar 記憶體位置 0x03 value 寫入記憶體位置 0x01 的 value => 記憶體指標 0x02
這邊可以發現 foo 跟 bar 的 value 都是 記憶體指標 0x02。
此時修改 bar.name 為 good,原先我們預期只有更改 bar.name 的值。
bar.name = 'good';
console.log(bar.name); // good
console.log(foo.name); // good !?
結果卻是連同 foo.name 都被改成 good 了 !?
這是因為現在 foo 跟 bar 的記憶體位置 value 都是記憶體指標 0x02,所以都是參考同一位置。異動後,其他變數只要是記憶體指標指向 0x02 都會被異動。
深層複製
要處理這種情況,一般會使用 for loop 來將 value 做複製。
例如
var foo = {name: 'into', age: 18};
var bar = {};
for (var i in foo) {
bar[i] = foo[i];
}
這樣就可以做到複製效果,讓兩個變數內容一樣,但是實際上記憶體指標是脫鉤的。
var foo = {name: 'into', age: 18, cards: [1,2,3,4]};
var bar = {};
for (var i in foo) {
bar[i] = foo[i];
}
bar.cards[0] = 99;
console.log(bar.cards); // [99,2,3,4]
console.log(foo.cards); // [99,2,3,4] !?
看似完美的複製,遇到多層物件的時候就破功了,這是淺層複製的問題所在。
兩個深層複製的方法
- 使用 recusive 遞迴方式處理每一個物件或是陣列
- 使用 var bar = JSON.parse(JSON.stringify(foo))
第二個方法要記得因為先轉為 JSON 再轉回來,所以要是有 Function 會因為這樣消失喔!