iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Modern Web

就是要搞懂 JavaScript 啦!系列 第 28

Day28 Object 複製物件:要深淺焙還是淺焙...我是說拷貝啦

  • 分享至 

  • xImage
  •  

傳值 v.s. 傳址

在程式中,傳遞內容基本上有兩種方法,一種是傳值,另一種則是傳址(又稱傳參):

  • 傳址/傳參(Call by reference / Pass by reference):傳遞的內容為記憶體參考,而非實際值。
  • 傳值(Call by value / Pass by value):傳遞的內容為除 object 外的任意基本型別,非變數也非物件屬性的「純值」。

使用傳址的好處是節省記憶體,不需要開闢那麼多額外空間,而且使用非常方便,只要對應同個參考就好,如果多個地方共用相同的內容,要修改時也只需要更改一處,非常方便。

與此相對的,由於記憶體指向的是同一個位置,容易不小心改變某個由多個物件共用參考的內容,而非專屬於該物件的內容。


複製物件

前面已經提過,JS 物件實際上並不儲存任何值,而是將這些值的記憶體參考作為「屬性」存在物件內部,也就是所謂的「傳址/傳參」。

因此在複製物件時,如果僅複製記憶體參考,兩個物件內部的值實質上是指向同一個記憶體空間,就稱為「淺拷貝」。

而「深拷貝」則是將原本的值本身完整複製出來,並放到另一個記憶體空間,其結果會與原複製物件指向完全不同的參考。

  • 淺拷貝:僅複製了參考,複製內容共用同個記憶體空間,概念類似於超連結。
  • 深拷貝:完整複製了值與結構,複製內容使用不同記憶體空間,概念類似於克隆。

淺拷貝(Shallow Copy)

淺拷貝是 JS 中物件最常見的複製方式,參考以下範例:

function anotherFunction() { /*..*/ }

var anotherObject = {
	c: true
};

var anotherArray = [];

var myObject = {
	a: 2,              // 純值
	b: anotherObject,  // 參考
	c: anotherArray,   // 參考
	d: anotherFunction // 參考
};

上面程式碼中,我們最後得到了一個新物件 myObject,它的 a 是個數字純值,但 bcd 都只是指向內容儲存位置的參考,因此這三個屬性的內容都屬於淺拷貝。

以下就來介紹一些淺拷貝的常見方式:

Object.assign()

使用 Object.assign() 時,可以複製一個或多個物件自身的屬性到另一個目標物件上,回傳值為該目標物件。

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 },回傳一個新物件
console.log(o1);  // { a: 1, b: 2, c: 3 },目標物件本身也被改變

當合併物件包含同名屬性時,後傳入的物件屬性會覆蓋前面:

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

傳入 undefinednull 作為參數時會被忽視:

var o1 = { a: 1 };

var obj = Object.assign({}, null, undefined, o1);
console.log(obj); // { a: 1 }

傳入基本類型作為參數時,只有可枚舉的類型(字串)會被複製:

var o1 = Object.assign({}, "abc");
var o2 = Object.assign({}, true);
var o3 = Object.assign({}, 10);
var o4 = Object.assign({}, Symbol('foo'));

console.log(o1); // { '0': 'a', '1': 'b', '2': 'c' }
console.log(o2); // {}
console.log(o3); // {}
console.log(o4); // {}

使用展開運算符

使用展開運算符「...」是另一種快速複製物件的方法,具體用法可參考這篇文章


深拷貝(deep copy)

由於物件可以有多層形式,在許多常見的狀況下,物件的拷貝經常都是淺拷貝,複製的是記憶體參考而非值本身。

如果要進行深拷貝,一個最常見且安全的方式是使用 JSON 格式轉換。也就是將一個物件序列化為一個 JSON 字串,之後再重新解析為擁有相同結構和值的物件:

var obj = { a: "A" };
var jsonStr = JSON.stringify(obj);
var newObj = JSON.parse(jsonStr);

console.log(obj);  // { a: 'A' }
console.log(newObj); // { a: 'A' }
console.log(obj === newObj); // false

參考資料


上一篇
Day27 Object 內建物件:一直在這只是你沒發現
下一篇
Day29 Object 屬性存在性:我思故我在...那麼屬性呢?
系列文
就是要搞懂 JavaScript 啦!73
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言