---
# System prepended metadata

title: 我要長大 - 物件觀念篇

---

# 我要長大 - 物件觀念篇

## 物件的傳值與傳參考

### 傳值

```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
```