---
# System prepended metadata

title: 12_以陣列 Array 的複製談型別_下
tags: ['鐵人賽 ']

---

# 12_以陣列 Array 的複製談型別_下
###### tags: `鐵人賽 `

###### Day 12

> 拿到它，先看它是什麼？拿它來做什麼? 再決定。

在複製陣列之前，先理解了 JavaScript 會依資料型別 Primitive type（基本型別）和Non-primitive type（非基本型別）而有不同的運作方式，我們就可以進一步的來了解，我們常聽到的深拷貝（DeepCopy)和淺拷貝(shallow) 是什麼、在什麼情境下要選擇哪一種複製方法、如何實作出這些深拷貝和淺拷貝。

## 什麼是拷貝？
在生活中我們最常做的拷貝就是影印，假如我有一份筆記，朋友跟我借去拷貝，當他拿著我的筆記，到影印機前，按下拷貝鍵的那一刻，複製的筆記從機器跑出來，這時「拷貝」這件事就算完成，「我的筆記」和「他影印的筆記」是各自獨立的筆記，之後他要在這份影印的筆記上塗鴉也好、修改也好，也不會影響我的筆記。

但是在 JavaScript 裡，當我們新建立一個變數`a`，賦予這個變數一個值，然後再建立一個新變數`b`，接著以`=`指定運算子來把`a` 指定給`b`，這樣就算拷貝嗎？雖然我們`a`、`b`叫出來看都是ˋ42ˋ，但是這真的是拷貝嗎。？是或不是？
```javascript
let a = 42;
let b = a;
a; // 42
b; // 42
```
### 被複製的資料型別是重點
事實上，這得要看我們要複製什麼東西，才能知道我們是否可以「輕易」的複製，且「真正」的把複製和被複製的徹底分離。
更確切的說，如果我們要複製的變數值型態，是屬於Primitive data types（基本資料型別），也就是以下的資料型態：
- Number (數字)，例如 `42`
- String (字串)，例如 `"Hi"` 
- Boolean (布林值)，例如 `true`
- undefined
- null

那麼我們就可以放心的以上述的方式複製。

還記得前一篇提到的 Call by value（呼叫變數的值）嗎？這些資料型別在 JavaScript 裡是屬於 Immutable (不可變)的基本型別，以值的方式複製可以得到真正的、獨立的複製。

### 你以為你複製了，但是並。沒。有。
但是，如果今天要被複製的資料型態是 Non Primitive data types（非基本資料型別），也就是 Object (物件型別)，那就無法完全複製。例如：
- Array (陣列)，例如 `[1, 2, 3, 4, 5]`
- Object (物件)，例如 `{ name : "Tsuifei"}`

因為陣列和物件是屬於 Call by reference（呼叫變數的記憶體位址），也就是說當我們複製這類型的資料，只是複製了這個變數的記憶體位置，所以當我們呼叫複製和被複製的變數時，都會指向同一個記憶體位置，當然，裡面的值也是同一個值，改任何一個，都會動到兩個變數的值。
我們再來複習一下前一篇的範例：
```javascript
// by reference(參考值)
let person = {
    name: "Tracy",
    city: "Tainan"
}

let person2 = person; 
person2.name = "Ayda"

person; // name: "Ayda"
person2; // name: "Ayda"
```
我們可以看到，在我們修改從`person`複製出來的`person2`時，`person`也被修改了。

## 物件專用的深拷貝和淺拷貝
終於，我們要進入 ~~深眠和淺眠~~ 這個正題了。
不知大家有沒發現，在討論這個深淺拷貝的範例時，清一色都是用物件來示範？原來，「深拷貝」和「淺拷貝」是針對物件的資料型別複製時，所產生的現象而來的啊！

但是要如何在 JavaScript 中區分深拷貝和淺拷貝？何時該用「深拷貝」或「淺拷貝」，用最簡單的方式是取決於我們想要複製的資料`[元素]`是什麼型別。 ~~結束。~~

### 淺拷貝 [ ] 只要一層都好說
完全的複製 Array 而不受原陣列影響，即使修改複製過來的物件裡的值，也不會改變複製來源，這個物件裡面的「元素」可以是任何一種資料型態，反著說，就是這個物件裡面的元素，不能是物件。如果遇到這樣的資料，就可以用淺拷貝的方法複製。

有哪幾種方法可以做淺拷貝？最常被拿來用的是 JavaScript 內建的陣列方法`slice()`。 ~~它的詳細解說會在後幾章才會介紹到。~~ `slice()`通常拿來做從陣列中切取我們需要的元素出來，在這裡我們使用`slice(0)`表示我們要從頭到尾都切下來。 ~~切切切~~

在下面第一個範例，「淺拷貝」是可行的，因為`arr1`陣列裡的元素是基本資料型別`Number`
```javascript
let arr1 = [1,2,3];
let arr2 = arr1.slice(0);

arr2[0] = 42;
arr1; // [1, 2, 3]
arr2; // [42, 2, 3]
```
但是以下這個範例，`arr1`陣列裡的「元素」是「物件型別」的陣列，淺拷貝對於原物件裡面的元素值是物件型態就是不行！ ~~噠美噠美~~
```javascript
// 淺拷貝 []
let arr1 = [[1,2,3],[4,5,6]];
let arr2 = arr1.slice(0);

arr2[0][0] = 42;
arr1; 
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]
arr2; 
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]
```
網路上能找到的大多是淺拷貝的例子，雖然淺 ~~可別因此就鄙視它~~，只要確認要複製的來源物件，裡面的元素不是物件型別，還是非常好用的。
礙於篇幅，這裡只介紹一種淺拷貝的方式，有興趣的朋友可查找網路上其他的方法，例如用解構式、迴圈或使用`map()`都可達到淺拷貝的效果。

### 深拷貝 [ [ ],[ ] ] --> DNA被複製，桃莉羊出現了
何時使用深拷貝？當我們想要完全複製一份「物件」裡面的元素也是「物件」，就可以使用深拷貝，這種情境就是我們在本文開頭所說的，用影印機複製筆記ㄧ樣，複製完就是兩個獨立的個體了。

做深拷貝的方法並不多，大部分都是靠外來的函式庫來撐腰，例如 lodash 和 jQuery 的第三方主流函式庫，如果使用原生的 JavaScript 來做深拷貝，似乎只能使用`JSON.stringify()`和`JSON.parse()`的交互作用，達到深拷貝的效果。

來看一下 MDN 對這兩個函式的解釋： [JSON.stringify()| MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) ｜ [JSON.parse() | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)

> `JSON.stringify()`方法是將一個 JavaScript 的值(物件或陣列)轉換為一個`JSON`的字串。
```javascript
let arr = [[1,2,3],[4,5,6]];
arr = JSON.stringify(arr); // "[[1,2,3],[4,5,6]]"
```

> `JSON.parse()`方法用來解析`JSON`的字串，構造由字串描述的JavaScript值或物件。

這時的`arr`已經變成JSON的字串格式：`"[[1,2,3],[4,5,6]]"`。
接下來再轉回陣列的型態：
```javascript
arr = JSON.parse(arr); // [[1,2,3],[4,5,6]]
```

把原本的物件值轉成字串，然後再轉回來物件的型態，兩個函式手牽手處理下來，就等於複製了一份`arr1`到`arr2`。
```javascript
function jsonDeepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
let arr1 = [[1,2,3],[4,5,6]];
let arr2 = jsonDeepClone(arr1);

arr2[0][0]=42;
arr1;
// 0: (3) [1, 2, 3]
// 1: (3) [4, 5, 6]
arr2;
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]
```
這樣的一個拷貝過程與結果就是深拷貝（DeepCopy）了。

優比～週末了！但是別人過週末，我們還是要過鋼鐵，明天繼續囉～

> 如有需要改進的地方，拜託懇求請告知，我會盡量快速度修改，感謝您～ 