---
title: 'JS 核心 13 - 求值策略、淺層複製及深層複製'
tags: JS 核心 ,JS , JavaScript, ES6
description: 2021/02/06
---
JS 直播班 -- 求值策略、淺層複製及深層複製
===
## 求值策略
> Call by Reference 還是 Call by Sharing
### [求值策略](https://zh.wikipedia.org/wiki/%E6%B1%82%E5%80%BC%E7%AD%96%E7%95%A5)
* 傳值呼叫(Call by value): JavaScript **純值**屬於此類
* 把值複製到新記憶體區域
* 傳參照呼叫(Call by reference)
* 傳遞給函式的是它的實際參數的隱式參照而不是實參的拷貝
* 傳共享物件呼叫(Call by sharing): JavaScript **物件**屬於此類
* 把物件定義出來給不同的變數使用
* 如果要達成傳參照呼叫的效果就需要傳一個共享物件,一旦被呼叫者修改了物件,呼叫者就可以看到變化(因為物件是共享的,沒有拷貝)
### 總結
object 的話,基本上是 call by reference,唯一要注意的就是<span class="red">**重新賦值**</span>這個動作造成 reference 改變的影響 → <span class="red">變成 **call by sharing**</span>。
---
## 淺層複製及深層複製
> 物件有傳參考特性,有時會在我們想要複製物件,並且想要這個複製的物件獨立於原本的物件時會造成一些困擾 (需將兩物件完全拆離開來)... 這樣要如何解決此問題呢 ?? 以下提供幾種複製的方法,解決這個問題。
### 淺層複製 Shallow Copy
#### 透過 for in 語法,做物件的淺層複製
<span class="red">**只能做第一層的複製,第二層仍舊是用傳參考的方式傳入的**</span>( call by reference)
```typescript=
var family = {
name: '小明家',
members: {
father: '老爸',
mom: '老媽',
ming: '小明'
},
}
var newFamily = {}; // 先新建一個物件
```
<span class="red"> **key** 指的是物件中的**第一層屬性名稱**,</span>第一層有幾個屬性、就跑幾次...
淺層複製只會複製純值,淺層複製也會宣告新的記憶體空間
內層物件只會傳參考
```typescript=10
for (var key in family) { // 宣告一個 key,key 值包含物件的第一層屬性內容
console.log(key);
// name members ( key 值包含 family 物件第一層的屬性 )
console.log(family[key]); // 讀出屬性所對應的內容
// 小明家
// {father: "老爸", mom: "老媽", ming: "小明"}
newFamily[key] = family[key]; // 將值塞入 newFamily 之後,就完成淺拷貝
}
console.log(family, newFamily); // 兩個物件內容一樣
newFamily.name = '杰倫家'; // 只能更改第一層內容
console.log(family, newFamily); // (如下圖)
console.log(family === newFamily); // false
```

newFamily 和 family.members 物件其實仍指向同一個參考位置,所以 family 的 members 內容也被修改了。
```typescript=22
newFamily.members.ming = '大明'; // 第二層還是「傳參考」模式
console.log(family, newFamily);
```

### 除了for in 語法外,也可以透過 jQuery 或是 ES6 寫法可以達到同樣的目的。
> 插入空物件,再把 family 物件傳入
#### ☞ jQuery 寫法
```
var newFamily2 = jQuery.extend({}, family);
```
#### ☞ ES6 寫法
```
var newFamily3 = Object.assign({}, family);
```
#### ☞ ES6 object.forEach() 寫法
```
var array = [];
family.forEach((item)=>{ // 淺層複製,相當於 for in 的語法
array.push(item);
})
```
### 深層複製 Deep Copy
> 將原本的物件轉為字串,再轉回物件,這樣傳參考的特性就消失了。
使用 JSON 方式把物件轉為字串,再轉回物件
```
var family = {
name: '小明家',
members: {
father: '老爸',
mom: '老媽',
ming: '小明'
},
}
console.log(family, JSON.stringify(family));
// 轉為字串,此 family 字串和原本的 family 物件已無參考關係
console.log(family, JSON.parse(JSON.stringify(family)));
// 再把 family 字串轉為物件 (和原本的 family 物件已無參考關係)
```

```
var newFamily = JSON.parse(JSON.stringify(family));
console.log(family === newFamily); // false,兩者已無關聯性
newFamily.name = '杰倫家';
newFamily.members.ming = '大明';
console.log(family, newFamily); // (如下圖) 兩者已無關聯,參考位置也不一樣
```

### 如果"類陣列"想要當陣列用,可以使用展開語法 [...arguments] 列舉並深層複製成一個新的陣列
```
function updateEasyCard() {
let arg = [...arguments];
let sum = arg.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log('我有 ' + sum + ' 元');
}
updateEasyCard(0); // 我有 0 元
updateEasyCard(10, 50, 100, 50, 5, 1, 1, 1, 500); // 我有 718 元
```
## :memo: 學習回顧
:::info
* 傳值呼叫(Call by value): **純值**屬於此類
* 把值複製到新記憶體區域
* 傳參照呼叫(Call by reference) : **物件**屬於此類
* 傳共享物件呼叫(Call by sharing): **物件**屬於此類
* <span class="red">**重新賦值**</span>這個動作造成 reference 改變的影響 → <span class="red">變成 **call by sharing**</span>。
* 淺層複製 Shallow Copy : 只能做第一層的複製,第二層仍舊是用傳參考的方式傳入的
* 透過 for(var key in object) 語法,做物件的淺層複製
* key 指的是物件中的第一層屬性名稱
:::
## :+1: 相關參考文件
:::info
[call by value 還是reference 又或是 sharing?](https://medium.com/@mengchiang000/js%E5%9F%BA%E6%9C%AC%E8%A7%80%E5%BF%B5-call-by-value-%E9%82%84%E6%98%AFreference-%E5%8F%88%E6%88%96%E6%98%AF-sharing-22a87ca478fc)
[Object.keys() & Object.values() & Object.entries()](https://ithelp.ithome.com.tw/articles/10239942)
[重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」](https://ithelp.ithome.com.tw/articles/10191057)
[關於 JS 中的淺拷貝和深拷貝](https://larry850806.github.io/2016/09/20/shallow-vs-deep-copy/)
:::
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>