---
# System prepended metadata

title: '[JavaScript] pass by value（傳值）、pass by reference（傳址）還是 pass by sharing'
tags: [前端筆記]

---

# [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` 但我還是屬於基本型別的喔

![](https://i.imgur.com/wgFJmbT.png)

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

![](https://i.imgur.com/7zqB674.png)

我是等到需要「認真地判別」才會覺得陣列跟函示是物件，平常寫 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;
```

![](https://i.imgur.com/HdjytVR.png)


依照「變數名稱是指向保存值的記憶體」的原則來看，實際的樣子應該是：
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）==

![](https://i.imgur.com/NCt1Sov.png)

為什麼不在原本的記憶體（0x001）內更改值呢？
**因為基本型別的值是不可改變的**，所以該記憶體的值就永遠是 12，如果要更新值，那麼 JavaScript 會重新開一個記憶體保存新的值。


那如果是物件型態呢？
```javascript=
let myArr = [];
```

接下來就解鎖新的觀念了，就是「Call Stack」及「Heap」。

#### Call Stack 跟 Heap 是啥米？
**Call Stack 保存基本型別**
```javascript=
let myNum = 12;
```
上面的例子畫成圖是這樣子：

![](https://i.imgur.com/RNcG5sZ.png)

流程都和上面相同，只是顯示出保存的位置，也就是「Call Stack」。

**Heap 則是保存物件型別**
回到例子
```javascript=
let myArr = [];
```
此例畫成圖則是：

![](https://i.imgur.com/NINDTpC.png)
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);
```

![](https://i.imgur.com/04uXlM1.png)


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

![](https://i.imgur.com/id4ctSb.png)

![](https://i.imgur.com/V4WOpog.png)

為了防止記憶體大爆炸，當資料型別是基本型別時，JavaScript 會自動尋找有沒有相同的值，如果有的話就不會多開記憶體了。

**不要搞混是比較值（Call Stack），而不是比較記憶體！**


當值是「布林值」時。
![](https://i.imgur.com/JL1XmBp.png)

![](https://i.imgur.com/FcGUgxd.png)


剩下「字串」、「undefined」以及「null」也是。
![](https://i.imgur.com/Krh72l6.png)

| identifier | address | value |
|:---------:|:--------:|:------|
|  | 0x01 | undefined |
|  | 0x02 | null |
|  | 0x03 | 'Hello World' |

### 2. 更新及傳遞不會改變原來的值

![](https://i.imgur.com/pfPWPVd.png)

![](https://i.imgur.com/GqZnAfW.png)

因為基本型別的值不可以被改變，所以 JavaScript 會重新建立新的記憶體保存新的值（20），`x` 也會重新指向新的記憶體。


## 物件型別（Object type）

![](https://i.imgur.com/CxQS1F8.png)

![](https://i.imgur.com/OcIjO1u.png)

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。**


![](https://i.imgur.com/j8pPMdv.png)

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

![](https://i.imgur.com/12mjpyc.png)


### (.)Dot Notation 跟 物件實字（Object Literal）大不同

**如果使用物件實字（{}）或者陣列實字（[]）賦值會導致新增一個新的物件 + 新增新的址參器。**

![](https://i.imgur.com/Dka0kCX.png)

![](https://i.imgur.com/ANs4DjI.png)

因為使用物件實字 = 創建一個新的物件，JavaScript 就會新增一個記憶體保存新的物件，此時 `objA` 就會指向新的記憶體（0x002），0x002 的值 也會時新的 Heap 的記憶體 heapx002。


同理，`function swap` 中是「物件實字」，是重新給新的物件（所以保存的就是新的址參器），但是函式中的 `obj` 變數只存活在函式中，脫離函式就掛了，所以在外層 `console.log(obj)` 就會報錯。
但是在 `function swap2` 中是 (.)Dot Notation，代表更新物件中屬性的值（value），所以函式中的 `obj` 指向的址參器和 `obj2` 是同一個。

![](https://i.imgur.com/JevfZou.png)

![](https://i.imgur.com/AoI9F92.png)

JavaScript 係按照值（也就是 Call Stack 的 value）來傳遞引數，當叫用一個函式，就代表函式會複製參數的值給引數。如果值是基本型別，如果在函式中更改值，並不會更改到函式外層的值。如果是物件型別，則有可能會更改，因為物件型別可以被更改的，Call Stack 中的 value 只是保存 Heap 中的 Address 而已。

「要注意的是引數脫離函式就掛了」所以函式外面撈不到 `obj`。

![](https://i.imgur.com/BWnRXXo.png)

![](https://i.imgur.com/tD7JxoH.png)


所以得到這樣子的結果也不意外了。
A: 一開始 `arr2` 確實和 `arr1` 一樣指向同個記憶體，但是後來 `arr1` 變指向和 `arr3` 相同的記憶體了。


## 番外篇，電腦看待變數與人類看待變數
**宣告變數就是告訴電腦：大大請把「位址」和這個代名詞連結吧**

>對電腦來說，實際運作和辨認資料位置是以「位址」為準；變數名稱只是為了方便人類呼叫、類似別名的存在，由程式幫忙和實際的資料儲存位址作連結。 ->ref:[你不可不知的 JavaScript 二三事#Day26：程式界的哈姆雷特 —— Pass by value, or Pass by reference？](https://ithelp.ithome.com.tw/articles/10209104)

因為 JavaScript 可以分成「基本型別」及「物件型別」，變數保存的東西不同，圖像畫可以變成：

![](https://i.imgur.com/8R3OCve.png)

- 電腦：變數是一個保存資料的位址（地址），如何保存資料要看「資料型別」是什麼
- 人腦：變數是一個代名詞，這個代名詞指向儲存資料的「位址」。因為人類宣告變數（也就是告訴電腦位址要和這個代名詞連結），所以人類可以藉由代名詞讀取「位址」中的值。

## 小記
那麼一定要非得釐清是 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)