owned this note
owned this note
Published
Linked with GitHub
# <font class="h2">by reference (傳參考)、by value(傳值)</font>
###### tags: `javascript`
<style>
.h2 {
background: linear-gradient(135deg,#fff,#537479) ;
color: #537479;
display:block;
padding: 6px 5px;
border-radius: 4px;
}
.h3 {
background: linear-gradient(180deg,#fff 50%,#c9d5d4) ;
color: #537479;
display:block;
padding: 6px 5px;
border-bottom: 3px solid #537479;
}
.h4 {
color: #537479;
font-weight:bold;
font-size:1.1em;
}
</style>
javascript的求值策略(Evaluation strategy)
- 傳值呼叫 (Call by value)-拷貝
- 傳參照呼叫 (Call by reference)-隱式參照
- 傳共享物件呼叫(Call by sharing)
:::info
- 傳值參考:複製內容到新的記憶體位置上
- 傳址參考:複製參考到新的記憶體位置上,但修改或賦予新值都會改變原有參考
- 共享參考:複製參考到新的記憶體位置上,修改會改變原有參考,但賦予新值則會指向新參考的記憶體位置
:::
在javascript裡面,有許多種「資料型別(data type)」,但主要分兩大類,一種是原始型別(primitive type),另一種是物件(Object)Primitive type以外的,例如array, function, map …etc。

<br>
### <font class="h3">傳值(by value)</font>
在JS裡,以下這六個原始型別(Primitive Type)皆屬於傳值。
- Boolean 布林值
- String 字串
- Number 數字
- Null 空值
- Undefined 未定義
- Symbol 符號 (ES6 新型別)
**範例:**
```javascript
let a = 1;
let b;
b = a;
console.log(a);//1
console.log(b);//1
```
當我們宣告一個變數`let a = 1`時,電腦會幫我們預留一個記憶體空間(e.g. 0x001),並且讓我們可賦值(1),若無賦值就是`undefined`。
當宣告完變數a, b時,當我們將變數a賦值給變數b時(`b = a`);,電腦會給變數b一個新的記憶體空間(e.g. 0x002),並複製變數a的值給變數b。

因此,實際上在電腦裡有兩個記憶體空間給變數a, b,其值皆為1。這就是所謂的傳值(by Value),其他型別除物件(object)外,都是以這種方式與電腦底層互動的。
<br>
### <font class="h3">傳址(傳參考) reference </font>
物件型別屬於傳址的類型,而包含物件本身共有三個型別皆屬於此類。
- 物件 Object
- 陣列 Array
- 函式 Function
```javascript
let c = {name:'Tom'}//宣告變數c並賦值一個物件
let d;
d = c;//賦予值
console.log(c);//{name:'Tom'}
console.log(d);//{name:'Tom'}
c.name ='Amy'//修改值
console.log(c);//{name:'Amy'}
console.log(c);//{name:'Amy'}
```

宣告變數c並賦值一個物件,再用變數c賦值變數b,結果變數c, b值會相同
從圖中我們得知,雖然變數c, d值相同,但因後來被宣告的變數d已「指向」變數c,且皆為物件型別。因此變數d在電腦底層實際上會指向與變數c相同的記憶體空間。
<br>
:::info
**例外:改變整個物件的話,會變則是「Pass by Value」:**
```javascript
let c ={name:'Tom'}
let d;
d = c;
console.log(c);//{name:'Tom'}
console.log(d);//{name:'Tom'}
d = {nickName:'Daniel'};//重新賦予一個新的物件
console.log(c)//{name:'Tom'}
console.log(d)//{nickName:'Daniel'}
```
從上述例子來看,一樣宣告變數c, d並賦值相同的值,但後續透過物件實體(Obeject Literal)的語法,直接用全新的值賦值變數d,從最終印出的結果可發現,變數c並不會因變數d的值重新被賦值而同步修改。

<br>
:::spoiler 補充說明: let與const的差異
:::info
**let與const的差異**
- 當是「值」時,值會變的使用let,值不會變的使用const
- 是「物件」的話,指向會變的用let,指向不會變的使用const
**物件傳參考特性(by reference)**
```javascript
const a = { name: '卡斯伯'}
//宣告一個物件
a.name = 'Ray'
//改變物件的值
```
這要寫是正確的
`{}`為「物件」,當宣告一個物件時,因為物件有傳參考特性,所以建議使用const,而不用let
<br>
<br>
**修改整個「物件」的話 不可以用 const 宣告**
```javascript
const a ={name:'卡斯柏'}
a = {name:'Ray'}
```
這樣寫會出錯,因為物件傳參考
`{}`為物件,產生一個新的物件,就會有一個新的參考位置(記憶體空間),:bulb:cont不勻許「物件指向」被改變,「物件指向」會被改變使用let

:BULB:結論:可以用 const 就用 const 不要都用 let 宣告
:::
<br>
### <font class="h4">➤範例一 </font>
**步驟一:定義物件變數family**
```javascript
var family = {
name :'小明家',
members:{
father:'老爸',
mom:'老媽',
ming:'小明',
}
}
```

<br>
**步驟二:定義物件變數member,將物件變數family裡的members賦予member**
```javascript
var family = {
name :'小明家',
members:{
father:'老爸',
mom:'老媽',
ming:'小明',
}
};
var member = family.members;
```

**步驟三:定義物件變數member重新賦值**
```javascript
var family = {
name :'小明家',
members:{
father:'老爸',
mom:'老媽',
ming:'小明',
}
};
var member = family.members;
member = {
ming : '大明'
}
```

<br>
### <font class="h4">➤範例二-無限循環範例 </font>
```javascript
var a = { x:1 };
a.y = a;
console.log(a);
```

<br>
### <font class="h4">➤範例三連續賦值 </font>
```javascript
var a = { x: 1 };
var b = a;
a.y = a = { x:2 };
//1. a = { x:2 } 是一個運算式
//2. a.y = a = { x:2 };是同時執行的,也等於a = a.y = { x:2 };
//3. a.y找的是原本的參考路徑(0x001)
console.log(a.y);//undefined
console.log(b);//{x:1; y:{x:2}}
console.log(a === b.y);//ture
//驗證一下a、b.y的參考位置是相同的
```
:bulb: ==連續賦值是同時執行的==

<br>
### <font class="h4">➤連續賦值課堂作業 </font>
```javascript
var a = { x: 1};
var b = a;
a.x = { x: 2};
a.y = a = { y: 1};
console.log(a);
console.log(b);
```

<br><br><br><br>
### <font class="h3">陣列範例</font>
```javascript
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr2.push(4);
console.log(arr1); //[1, 2, 3, 4]
console.log(arr2); //[1, 2, 3, 4]
arr2 = null
console.log(arr1); //[1, 2, 3, 4]
console.log(arr2); //null
```
<br><br><br>
避免方法,使用slice
```javascript
var arr1 = [1, 2, 3];
var arr2 = arr1.slice();
arr2.push(4);
console.log(arr1); //[1, 2, 3]
console.log(arr2); //[1, 2, 3, 4]
```
<br><br><br><br>
### <font class="h3">call by sharing傳共用物件呼叫</font>
傳共享物件呼叫(Call by sharing)的方式由Barbara Liskov命名,並被Python、Java(物件類型)、JavaScript、Scheme、OCaml等語言使用。
如果要達成傳參照呼叫的效果就需要傳一個共享物件,一旦被呼叫者修改了物件,呼叫者就可以看到變化(因為物件是共享的,沒有拷貝)。
<br>
### <font class="h4">傳入函式範例 </font>
```javascript
let jediList = ['Anakin' , 'Luke' , 'Ahsoka']
function addFellow(list){
list.push('Yoda')
}
addFellow(jediList)
console.log('jediList',jediList)//['Anakin', 'Luke', 'Ahsoka', 'Yoda']
```
我們把陣列傳進函式裡面,並在函式內去修改這個陣列變數內容會影響函式外的陣列變數內容
<br>
```javascript
let jediList = ['Anakin' , 'Luke' , 'Ahsoka']
function addFellow(list){
list = ['nobody']
}
addFellow(jediList)
console.log('jediList',jediList) // ['Anakin' , 'Luke' , 'Ahsoka']
```
我們在函式裡面重新對變數指派了一個全新的陣列,卻不影響到函式外的變數內容。
這兩個行為讓javascript看起來不像是「傳值」、也不像是「傳參考」,於是就有人嘗試稱它為「Call By Sharing」
<br>
### <font class="h4">➤總結 </font>
- 基本型別就是「Pass by Value」(傳值)
- 物件型別,分兩個情況。
第一,若僅「修改」值則是「Pass by Reference」。
第二,若「重新賦值」(e.g. 物件實體 Object Literal/陣列實體 Array Literal)值則是「Pass by Value」。
<br><br><br><br>
### <font class="h3">陷阱題</font>
### <font class="h4">➤不要調整傳入物件屬性</font>
```javascript
var a = {
name: 'a'
}
function changeData(param) {
param.name = 'b'
return param
}
var b = changeData(a)
console.log(a === b) //true
```
<br>
### <font class="h4">➤forEach範例</font>
```javascript
const array = ['卡斯伯','小杰']
array.forEach((item)=>{
if(item === '小杰'){
item = 'Ray'
}
})
console.log(array[1]) //小杰
```
因該改成

<br><br>
---
參考來源:
https://hackmd.io/@bookbasketball/SyhGp0frF
https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/
https://youtu.be/fD0t_DKREbE
https://ithelp.ithome.com.tw/articles/10221506?sc=pt