# [JavaScript] pass by value(傳值)、pass by reference(傳址)還是 pass by sharing
###### tags: `前端筆記`
## 總結
- ==變數不是直接保存值,而是指向保存值的記憶體(限 Call Stack 的區域)==
- value 的定義會影響是否有 pass by value, pass by reference or pass by sharing 還是一律只有 pass by value
- ==核心觀念:如果是基本型別,就是不可改變的,會新增記憶體;如果是物件型別,就是可改變的,除非新建立一個物件(物件實字 Obejct Literal 或者陣列實字 Array Literal),就不會新增記憶體。==
## 事前須知
談到這三個觀念之前必須先探討 JavaScript 的資料型別,因為資料型別會決定一個變數是用什麼方式傳遞的。JavaScript 的資料可以分成「基本型別」以及「物件型別」兩大類。因為資料型態的不同也會影響變數儲存值的方式,所以還要理解 JavaScript 是如何「記憶」變數的。
### 1.資料型別的探討
#### 1. 基本型別(Primitive data type)
1. 字串(String)
2. 數字(Number)
3. 布林值(Boolean)
4. undefined
5. null => 即使 `typeof` 出來的是 `object` 但我還是屬於基本型別的喔

#### 2. 物件型別(Object type)
除了基本型別之外都是物件型別
- 即使陣列及物件都有自己的方法可以用,但在 JavaScript 的眼中陣列就是物件
- 即使函式 `typeof` 得到的是 *function* 但在 JavaScript 眼中函式就是物件

我是等到需要「認真地判別」才會覺得陣列跟函示是物件,平常寫 code 就把他們想成是物件裡面個別的東西。
### JavaScript 是如何「記憶變數的」?
**不同的資料型態,值保存的位置會不一樣**
```javascript=
let myNum = 12;
```
一開始我認為的方式:
1. JavaScript 會建立一個記憶體(Memory,比方來說 0x001)
2. 0x001 會保存值(12)
3. JavaScript 再創造一個變數名稱(也就是 `myNum`)
4. `myNum` 會保存值(12)
結果仔細讀完這篇厲害的文章:[JavaScript’s Memory Model](https://medium.com/@ethannam/javascripts-memory-model-7c972cd2c239) 後,發現事情不是那麼簡單...
其實是另一個方式:
1. 建立一個 identifier (名稱就是宣告的變數名稱,也就是 `myNum`)
1. 會先判別值是什麼型別(基本型別 Primitive data type 或者物件型別 Object type)
2. 發現是基本型別
3. 在 Call Stack 建立一個記憶體(Memory,比方來說 0x001)
4. 0x001 保存值(12)
5. 將 `myNum` 指向「記憶體」0x001,讓人類可以藉由 `myNum` 找到記憶體 0x001 裡面的值
所以正確來說:`myNum` 是指向「記憶體」,不是直接指向「值」;當看到 `let myNum = 12;` 其實代表的是:==`myNum` 是指向一個保存的值是 12 的記憶體,而不是指向值。==
接下來再看這個例子
```javascript=
let myNum = 12;
let myNum2 = myNum;
```

依照「變數名稱是指向保存值的記憶體」的原則來看,實際的樣子應該是:
1. 建立一個 identifier (名稱就是宣告的變數名稱,也就是 `myNum`)
1. 會先判別值是什麼型別(基本型別 Primitive data type 或者物件型別 Object type)
2. 發現是基本型別
3. 在 Call Stack 建立一個記憶體(Memory,比方來說 0x001)
4. 0x001 保存值(12)
5. 將 `myNum` 指向「記憶體」0x001,讓人類可以藉由 `myNum` 找到記憶體 0x001 裡面的值
6. 建立一個 identifier (也就是 `myNum2`)
7. `myNum2` 指向的記憶體與 `myNum` 相同,兩個記憶體都保存相同的值
==這時 `myNum` 跟 `myNum2` 指向的記憶體(Memory)都相同(0x001),所以就有相同的值。==
如果現在是這樣子的話呢?
```javascript=
let myNum = 12;
let myNum2 = myNum;
myNum2 += 1;
```
現在就要理解另一個新的觀念了:
>All primitives are immutable, i.e., they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered. [ref. - MDN](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
>所有基本型別值是不可改變的,這個觀念對於理解一個保存基本型態值的變數非常重要。該變數也許會被重新賦值,但是原本的值是不會像物件型別一樣,是絕對不會被更改的。
==重點即是:所有基本型別的值是不會被改變的。==
套在這個例子上就是:
1. 建立一個 identifier (名稱就是宣告的變數名稱,也就是 `myNum`)
1. 會先判別值是什麼型別(基本型別 Primitive data type 或者物件型別 Object type)
2. 發現是基本型別
3. 在 Call Stack 建立一個記憶體(Memory,比方來說 0x001)
4. 0x001 保存值(12)
5. 將 `myNum` 指向「記憶體」0x001,讓人類可以藉由 `myNum` 找到記憶體 0x001 裡面的值
6. 建立一個 identifier (也就是 `myNum2`)
7. `myNum2` 指向的記憶體與 `myNum` 相同,兩個記憶體都保存相同的值
8. ==建立一個新的記憶體(0x002)保存值 1==
10. ==建立新的記憶體(0x003)保存 12 + 1 的結果(13)==
11. ==`myNum2` 指向新的記憶體(0x003)==

為什麼不在原本的記憶體(0x001)內更改值呢?
**因為基本型別的值是不可改變的**,所以該記憶體的值就永遠是 12,如果要更新值,那麼 JavaScript 會重新開一個記憶體保存新的值。
那如果是物件型態呢?
```javascript=
let myArr = [];
```
接下來就解鎖新的觀念了,就是「Call Stack」及「Heap」。
#### Call Stack 跟 Heap 是啥米?
**Call Stack 保存基本型別**
```javascript=
let myNum = 12;
```
上面的例子畫成圖是這樣子:

流程都和上面相同,只是顯示出保存的位置,也就是「Call Stack」。
**Heap 則是保存物件型別**
回到例子
```javascript=
let myArr = [];
```
此例畫成圖則是:

1. 建立一個 identifier (名稱就是宣告的變數名稱,也就是 `myArr`)
2. 接下來會判斷值是什麼型別(基本型別 或 物件型別)
3. 發現是物件型別!
4. 在 Call Stack 建立一個新的記憶體(比方來說:0x001)
5. 在 Heap 建立一個新的記憶體(比方來說:heapx001)
6. heapx001 保存物件型態的值([])
7. Call Stack 的 0x001 保存 Heap 的記憶體(heapx001)
8. `myArr` 指向記憶體(0x001)
**所以 `myArr` 是指向一個以 Heap 記憶體(heapx001)為值的 Call Stack 記憶體(0x001)。**
==而且與基本型態不同,物件型態是可以被改變的,所以當我們改變物件型態的值時,JavaScript 並不會為此新增一個記憶體。==
```javascript=
let myArr = [];
myArr.push(1);
myArr.push(2);
myArr.push(3);
```

## 基本型別(Primitive data type)
### 1. 依照「值」判別
當值是「數字」時。


為了防止記憶體大爆炸,當資料型別是基本型別時,JavaScript 會自動尋找有沒有相同的值,如果有的話就不會多開記憶體了。
**不要搞混是比較值(Call Stack),而不是比較記憶體!**
當值是「布林值」時。


剩下「字串」、「undefined」以及「null」也是。

| identifier | address | value |
|:---------:|:--------:|:------|
| | 0x01 | undefined |
| | 0x02 | null |
| | 0x03 | 'Hello World' |
### 2. 更新及傳遞不會改變原來的值


因為基本型別的值不可以被改變,所以 JavaScript 會重新建立新的記憶體保存新的值(20),`x` 也會重新指向新的記憶體。
## 物件型別(Object type)


1. 建立 identifiter (也就是變數名稱 objA, objB)
2. 在 Heap 建立記憶體保存值({x: 10})
3. 在 Call Stack 建立記憶體並保存 Heap 中的記憶體(也就是 Address)
4. `objA` 及 `objB` 指向對應的 Call Stack 記憶體
即便兩個物件的屬性(property = key + value) 都相同,但變數係保存物件的「址參器」並不是「物件本身」。兩個物件就會有兩個址參器,所以不相等。
**因為 `objA` 保存的 reference != `objB` 保存的 reference。**

一開始先宣告 `objB` 指向與 `objA` 相同的記憶體,因為物件型態可以被更改(Objects are mutable.)所以不會開新的記憶體保存更改的值,而是在原本的記憶體直接改值。

### (.)Dot Notation 跟 物件實字(Object Literal)大不同
**如果使用物件實字({})或者陣列實字([])賦值會導致新增一個新的物件 + 新增新的址參器。**


因為使用物件實字 = 創建一個新的物件,JavaScript 就會新增一個記憶體保存新的物件,此時 `objA` 就會指向新的記憶體(0x002),0x002 的值 也會時新的 Heap 的記憶體 heapx002。
同理,`function swap` 中是「物件實字」,是重新給新的物件(所以保存的就是新的址參器),但是函式中的 `obj` 變數只存活在函式中,脫離函式就掛了,所以在外層 `console.log(obj)` 就會報錯。
但是在 `function swap2` 中是 (.)Dot Notation,代表更新物件中屬性的值(value),所以函式中的 `obj` 指向的址參器和 `obj2` 是同一個。


JavaScript 係按照值(也就是 Call Stack 的 value)來傳遞引數,當叫用一個函式,就代表函式會複製參數的值給引數。如果值是基本型別,如果在函式中更改值,並不會更改到函式外層的值。如果是物件型別,則有可能會更改,因為物件型別可以被更改的,Call Stack 中的 value 只是保存 Heap 中的 Address 而已。
「要注意的是引數脫離函式就掛了」所以函式外面撈不到 `obj`。


所以得到這樣子的結果也不意外了。
A: 一開始 `arr2` 確實和 `arr1` 一樣指向同個記憶體,但是後來 `arr1` 變指向和 `arr3` 相同的記憶體了。
## 番外篇,電腦看待變數與人類看待變數
**宣告變數就是告訴電腦:大大請把「位址」和這個代名詞連結吧**
>對電腦來說,實際運作和辨認資料位置是以「位址」為準;變數名稱只是為了方便人類呼叫、類似別名的存在,由程式幫忙和實際的資料儲存位址作連結。 ->ref:[你不可不知的 JavaScript 二三事#Day26:程式界的哈姆雷特 —— Pass by value, or Pass by reference?](https://ithelp.ithome.com.tw/articles/10209104)
因為 JavaScript 可以分成「基本型別」及「物件型別」,變數保存的東西不同,圖像畫可以變成:

- 電腦:變數是一個保存資料的位址(地址),如何保存資料要看「資料型別」是什麼
- 人腦:變數是一個代名詞,這個代名詞指向儲存資料的「位址」。因為人類宣告變數(也就是告訴電腦位址要和這個代名詞連結),所以人類可以藉由代名詞讀取「位址」中的值。
## 小記
那麼一定要非得釐清是 pass by value, pass by reference or pass by sharing 嗎?在我看 [JavaScript’s Memory Model](https://medium.com/@ethannam/javascripts-memory-model-7c972cd2c239) 以前覺得是重要的,但是現在我認為理解 [JavaScript’s Memory Model](https://medium.com/@ethannam/javascripts-memory-model-7c972cd2c239) 中講解的 JavaScript Memory Model 才是重要的,因為這個觀念把 pass by value, pass by reference or pass by sharing 背後的原因講的更透徹,照這個原理,JavaScript 只有 pass by value,因為都是靠記憶體來存值的。不過就如同很多大神分享的,知道基本型態是不可以改變的,而物件型態是可以改變的(然後知道用物件實字及陣列實字是新增一個全新的物件),以及 JavaScript Memory Model,就可以了。
## 參考資料
1. [深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/) => 內有為什麼 JavaScript 係只傳值的介紹
2. [你不可不知的 JavaScript 二三事#Day26:程式界的哈姆雷特 —— Pass by value, or Pass by reference?](https://ithelp.ithome.com.tw/articles/10209104) => 有額外補充變數的介紹
3. [重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?](https://ithelp.ithome.com.tw/articles/10191057) => 基本型態及物件型態的更新、傳遞介紹
4. [Day.19 「認識 JavaScript 記憶體堆疊、傳值 與 傳址」 —— JavaScript 物件 與 記憶體](https://ithelp.ithome.com.tw/articles/10273943?sc=iThomeR)
5. [JavaScript’s Memory Model](https://medium.com/@ethannam/javascripts-memory-model-7c972cd2c239) =>特級大神的文
6. [Immutability in JavaScript](https://www.youtube.com/watch?v=rHt8O9oGOrk)