跳至主要内容

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的深拷貝處理後,兩邊的物件全部指向不一樣的記憶體位”址”,不管有幾層。
  • 所以即使修改任一層值,也不影響另一個物件。