JS「傳值」、「傳址」、深淺【拷貝】差別
JS的傳遞行為
首先在JS中,傳遞行為分為兩種:
- 傳值:Call by value 或是 Pass by value。
- 傳址:Call by reference 或是 Pass by reference。
在JS中,基本型別就是【傳值】、物件型別就是【傳址】
基本型別,傳值
基本型別包含:number、string、boolean、null、undefined、symbol
傳值範例:
let a = 10;
let b = 10;
console.log(a === b); // true
- 10 為基本型別,所以比較a、b時,會回傳true。
let a = 2;
let b = a;
b += 1;
console.log(a) // 2
console.log(b) // 3
- 當 b 的來源為 a 時, a 是基本型別
- b 得到的是值,而不是記憶體的位”址”
- 所以就算改變了b的值,也不會影響到a,兩者是獨立運作的
物件型別,傳址
物件型別包含:物件、陣列、函式、日期
傳址範例:
let obj1 = { a: 1 };
let obj2 = { a: 1 };
console.log( obj1 === obj2 ); // false
- 可以看到雖然 兩個屬性的key跟value都一樣是 a: 1 ,但結果卻是 false
- 原因是每個物件都是獨立的存在,兩者的記憶體位”址”並不相同
- 在比較 “物件型別” 時,比較的是記憶體的位”址”,而不是”值”
複製傳址
let obj = {
title: '錢錢',
amounts: 66666,
}
let objNew = obj;
objNew.amounts = 123;
console.log(obj.amounts); // 123
console.log(obj === objNew); // true
obj = {
title: '錢錢',
amounts: 66666
}
console.log(obj) // { title: '錢錢', amounts: 66666 }
console.log(objNew) // { title: '錢錢', amounts: 123 }
console.log(obj === objNew); // false
- 我們將 objNew 透過 objNew = obj 賦值
- 當我們修改任一邊的屬性時,另一邊的屬性也會跟著變動
- 原因是此時的兩個變數只向相同的記憶體位”址”
- 但當我們把其中一個變數賦予新的”物件”時,即使值相同,obj也會指向新的記憶體位置
- 而objNew依然是原本的記憶體位”址”,此時 obj 跟 objNew 彼此就沒有任何關係了,故結果為false
探討物件型別,傳址(淺拷貝、深拷貝)
複製物件分為兩類:
- 淺拷貝(Shallow Copy)
- 深拷貝(Deep Copy)
淺拷貝(Shallow Copy)
常見的淺拷貝方式:
- 使用 Object.assign()將原本的
obj
內容複製到一個空物件中。 - 在一個空物件內,使用
...
展開運算子展開物件。
let obj = {
title: '錢錢',
amounts: 66666,
}
let objNew = Object.assign({}, obj);
// let objNew = { ... obj }; // 效果一樣
console.log(obj) // { title: '錢錢', amounts: 66666 }
console.log(objNew) // { title: '錢錢', amounts: 66666 }
console.log(obj === objNew) // false
objNew.amounts = 123
console.log(obj) // { title: '錢錢', amounts: 66666 }
console.log(objNew) // { title: '錢錢', amounts: 123 }
console.log(obj === objNew) // false
- 可以看到經過淺拷貝複製後,兩個物件指向的記憶體位”址”已不同,故修改其中物件的值,也不影響另一個物件。
淺拷貝盲點
若我們在物件裡再放一個物件。
let obj = {
title: '錢錢',
amounts: 66666,
b: {
title: '糖糖',
amounts: 10000,
}
}
let objNew = Object.assign({}, obj);
// let objNew = { ... obj };
objNew.amounts = 123;
objNew.b.amounts = 123;
console.log(obj)
// { title: '錢錢', amounts: 66666, b: { title: '糖糖', amounts: 123 } }
console.log(objNew)
// { title: '錢錢', amounts: 123, b: { title: '糖糖', amounts: 123 } }
console.log(obj.b.amouts === objNew.b.amouts)
// true
- 可以看到兩個物件的第一層雖然已指向不一樣的記憶體位”址”,但第二層卻還是指向相同的記憶體位”址”。
- 所以當我們修改第二層的值,同樣會影響另一個物件的第二層值。
深拷貝(Deep Copy)
深拷貝就是完全複製一份,不會有共用記憶體的問題。
常見深拷貝物件方法有:
- 利用 JSON 方法:先轉 JSON 格式,再轉回來。
let obj = {
title: '錢錢',
amounts: 66666,
b: {
title: '糖糖',
amounts: 10000,
}
}
let objNew = JSON.parse(JSON.stringify(obj))
objNew.amounts = 123;
objNew.b.amounts = 123;
console.log(obj)
// { title: '錢錢', amounts: 66666, b: { title: '糖糖', amounts: 10000 } }
console.log(objNew)
// { title: '錢錢', amounts: 123, b: { title: '糖糖', amounts: 123 } }
console.log(obj.b.amouts === objNew.b.amouts)
// false
- 經過JSON的深拷貝處理後,兩邊的物件全部指向不一樣的記憶體位”址”,不管有幾層。
- 所以即使修改任一層值,也不影響另一個物件。