# 我要長大 - 物件觀念篇 ## 物件的傳值與傳參考 ### 傳值 ```javascript let person = "Tim"; let person2 = person; console.log(person2); //Tim person2 = "智庭"; console.log(person2); //智庭 ``` ### 傳參考 透過傳值的特性可以知道: ```javascript let person = { name: "Tim", }; let person2 = person; person2.name = "智庭"; console.log("person:", person, "person2:", person2); console.log("person === person2:", person === person2); ``` 1. 會得到相同物件。 2. 比對會得到 true。 ![](https://i.imgur.com/xOF6q5K.png) 但改寫成: ```javascript let person = { name: "Tim", }; let person2 = person; person2 = { name: "智庭", }; console.log("person:", person, "person2:", person2); console.log("person === person2:", person === person2); ``` 1. 已變成兩個不同的物件,原因是 person2 已變成不同的參考路徑。 2. 比對結果為 false。 ![](https://i.imgur.com/otpjx4F.png) ## 閉包 `Closure` 這個字詞是由 `Close` 與字尾 `-ure` 所構成,`-ure` 有動作、進行或結果的意思,如果 `close` 是關閉的意思,`closure` 就是關閉的結果或動作,它可以作為名詞或動詞使用,中文有封閉、終止或結束的意思。 在 JavaScript 中**每當函式被建立**時,**一個閉包就會被產生**,閉包是一個函式建立時的就有的**自然特性**。 ```javascript function easyCard(base) { let money = base; return function (update) { money = money + update; console.log(money); }; } let myEasyCard = easyCard(100); // 我原本有一百元 myEasyCard(50); // 儲值50元 ``` > 懶人記法:閉包的概念就是呼叫函式中的函式。 ### 工廠模式 不要直接呼叫,而是讓函式賦予在另一個變數上,就會將 `myMoney` 變數存在內層的作用域,在每次執行後不斷更新此值。 ```javascript function buyItem() { var myMoney = 1000; return function (price) { // 這裡就是一個閉包,不過目前只會使用一次 myMoney = myMoney - price; return myMoney; }; } let balance = buyItem(); // 存取內部函式的變數 console.log(balance(100)); //900 console.log(balance(200)); //700 console.log(balance(300)); //400 ``` 1. 不能使用 `buyItem()` 呼叫,因為執行結果會出現 `function ...` 的物件,沒辦法直接使用。 1. 透過 `balance` 變數,將 `buyItem()` 的值賦予給其變數,使其可以不斷的反覆呼叫,且內層記憶體不會被釋放。 1. `balance` 每次執行時,只會執行內層的函式,在內層記憶體沒有被釋放的情況下,`myMoney` 變數會不斷的被更新。 1. 透過給予預設值與固定流程,帶入參數後會得到值的執行過程,類似**工廠**一樣,又稱為工廠模式。 ### 私有方法 只有**單一種方法**,私有方法是將結果傳入到另一個物件內,**物件內的屬性都是一段函式**,在將要使用的方法寫入其內,並可在外部取用該方法得到想要的運算結果。 ```javascript function buyItem(initValue) { var myMoney = initValue || 1000; return { increase: function (price) { myMoney += price; }, decrease: function (price) { myMoney -= price; }, value: function () { return myMoney; }, }; } var balance = buyItem(100); balance.increase(1000); balance.decrease(500); console.log(balance.value()); ``` ## 深淺拷貝 data: ```javascript var family = { home: "提姆家", members: { dad: "Tim", mom: "Min", son: "Joshua", }, }; ``` ### 淺拷貝 #### for...in ```javascript for (const key in family) { console.log(key); //home members console.log(family[key]); // 提姆家 {dad: 'Tim', mom: 'Min', son: 'Joshua'} } ``` 建立一個新物件實字,使其值與 data 資料相同。 ```javascript var newFamily = {}; for (const key in family) { newFamily[key] = family[key]; console.log((newFamily[key] = family[key])); //提姆家 {dad: 'Tim', mom: 'Min', son: 'Joshua'} console.log(newFamily[key] === family[key]); //true } ``` 但此時兩者比對後為 false,代表已經變成不同的物件。 ```javascript var newFamily = {}; for (const key in family) { newFamily[key] = family[key]; } console.log(family === newFamily); //false ``` 會發現 `newFamily` 第一層的 `home` 屬性被修改了,但 `family` 沒有,因為這兩個物件已經沒有關係了。 ```javascript var newFamily = {}; for (const key in family) { newFamily[key] = family[key]; } console.log(family === newFamily); //false newFamily.home = "Tim's home"; console.log("family:", family); console.log("newFamily:", newFamily); ``` ![](https://i.imgur.com/ywY6hnx.png) ### 使用 ES6 的解構賦值也可達到一樣的效果 ```javascript var newFamily = { ...family }; newFamily.home = "Tim's home"; newFamily.members.dad = "智庭"; console.log("family:", family); console.log("newFamily:", newFamily); ``` ![es6](https://i.imgur.com/Wm0xL8V.png) ### 結束了嗎? 如果把 `newFamily` 裡面的 `members` 中的屬性 `dad` 的值改成**智庭**, ```javascript var newFamily = {}; for (const key in family) { newFamily[key] = family[key]; } newFamily.home = "Tim's home"; newFamily.members.dad = "智庭"; console.log("family:", family); console.log("newFamily:", newFamily); ``` 會發現原本 `family` 的 `dad` 的值也被改掉了。 ![](https://i.imgur.com/Q8azAmo.png) 原因是透過這樣的方式只能改變第一層的屬性內容,第二層資料內容還是以**傳參考**的形式,這個方式就叫做淺層複製,也有稱為**淺拷貝**。 ### 深拷貝 為了不要有物件傳參考的特性,會先把資料轉型成字串,再轉型成物件。 #### 轉字串 ```javascript let deepFamily = JSON.stringify(family); console.log(deepFamily); ``` #### 再轉成物件 ```javascript let deepFamily = JSON.stringify(family); let deepNewFamily = JSON.parse(deepFamily); deepNewFamily.members.dad = "智庭"; console.log("family", family); console.log("deepNewFamily:", deepNewFamily); console.log(family === deepNewFamily); // false ``` 會發現 family 第二層 members.dad 的值沒有被修改,代表兩個物件已經不同外,內層的資料也沒有關聯。 ![](https://i.imgur.com/1PYUKdG.png) ## 原型鏈 ES6 之前還沒有 `class`,是透過 `prototype` 來進行繼承與鏈接。 為了讓自己好記,用洛克人的遊戲來當情境。 ```javascript // 洛克人的武器是 buster 飛彈 var rockman = { buster: true }; // 剪刀人的武器是剪刀 var cutman = { cutter: true }; ``` 使用 `in` 來取得物件屬性,屬性名稱必須為字串。 ```javascript console.log( 'buster' in rockman ); // true console.log( 'cutter' in rockman ); // false ``` 可以看到洛克人現在沒有剪刀人的武器。 在打敗剪刀人後,要獲取剪刀人的武器,可以使用 `Object.setPrototypeOf()` 的方法,把剪刀人的設定為原型。 ```javascript Object.setPrototypeOf(rockman, cutman); ``` 第一欄參數是繼承者,第二欄位是原型物件。 **完整程式碼** ```javascript // 洛克人的武器是 buster 飛彈 var rockman = { buster: true }; // 剪刀人的武器是剪刀 var cutman = { cutter: true }; console.log( 'buster' in rockman ); // true console.log( 'cutter' in rockman ); // false // 指定 cutman 為 rockman 的「原型」 Object.setPrototypeOf(rockman, cutman); console.log( 'buster' in rockman ); // true // 透過原型繼承,現在洛克人也可以使用剪刀人的武器了 console.log( 'cutter' in rockman ); // true ``` ### 無法同一物件指定兩種原型物件 意思是說假設洛克人打敗剪刀人,現在又要打敗氣力人,然後希望在打敗氣力人後可以同時使用氣力人武器該怎麼做? ```javascript // 氣力人的武器是超級手臂 var gutsman = { superArm: true }; // 指定 gutsman 為 rockman 的「原型」 Object.setPrototypeOf(rockman, gutsman); // 這個時候洛克人也可以使用氣力人的超級手臂 console.log( 'superArm' in rockman ); // true // 但是剪刀卻不見了,哭哭 console.log( 'cutter' in rockman ); // false ``` ### 使用原型鏈 當要從某個物件要試著去存取「不存在」的屬性時,會往它的 `[[prototype]]` 原型物件去尋找。 ![](https://ithelp.ithome.com.tw/upload/images/20171227/20065504qAzPMYf2Ak.png) 此時使用 `Object.setPrototypeOf()` 方法來做鏈接,這樣就可以使用到氣力人的武器了。 ```javascript // 洛克人的武器是 buster 飛彈 var rockman = { buster: true }; // 剪刀人的武器是剪刀 var cutman = { cutter: true }; // 氣力人的武器是超級手臂 var gutsman = { superArm: true }; // 指定 cutman 為 rockman 的「原型」 Object.setPrototypeOf(rockman, cutman); // 指定 gutsman 為 cutman 的「原型」 Object.setPrototypeOf(cutman, gutsman); // 這樣洛克人就可以順著「原型鏈」取得各種武器了! console.log( 'buster' in rockman ); // true console.log( 'cutter' in rockman ); // true console.log( 'superArm' in rockman ); // true ```