--- 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 ``` ![](https://i.imgur.com/ED8hNwo.png) newFamily 和 family.members 物件其實仍指向同一個參考位置,所以 family 的 members 內容也被修改了。 ```typescript=22 newFamily.members.ming = '大明'; // 第二層還是「傳參考」模式 console.log(family, newFamily); ``` ![](https://i.imgur.com/tRGzdvH.png) ### 除了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 物件已無參考關係) ``` ![](https://i.imgur.com/SGEpg6O.png) ``` var newFamily = JSON.parse(JSON.stringify(family)); console.log(family === newFamily); // false,兩者已無關聯性 newFamily.name = '杰倫家'; newFamily.members.ming = '大明'; console.log(family, newFamily); // (如下圖) 兩者已無關聯,參考位置也不一樣 ``` ![](https://i.imgur.com/hhD1tUF.png) ### 如果"類陣列"想要當陣列用,可以使用展開語法 [...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>