# 我要長大 - 物件觀念篇
## 物件的傳值與傳參考
### 傳值
```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。

但改寫成:
```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。

## 閉包
`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);
```

### 使用 ES6 的解構賦值也可達到一樣的效果
```javascript
var newFamily = { ...family };
newFamily.home = "Tim's home";
newFamily.members.dad = "智庭";
console.log("family:", family);
console.log("newFamily:", newFamily);
```

### 結束了嗎?
如果把 `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` 的值也被改掉了。

原因是透過這樣的方式只能改變第一層的屬性內容,第二層資料內容還是以**傳參考**的形式,這個方式就叫做淺層複製,也有稱為**淺拷貝**。
### 深拷貝
為了不要有物件傳參考的特性,會先把資料轉型成字串,再轉型成物件。
#### 轉字串
```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 的值沒有被修改,代表兩個物件已經不同外,內層的資料也沒有關聯。

## 原型鏈
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]]` 原型物件去尋找。

此時使用 `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
```