---
# System prepended metadata

title: 核心 24 - 使用 Object.create 建立多層繼承
tags: ['JS 核心 ', 'JS ', ' JavaScript']

---

---
title: '核心 24 - 使用 Object.create 建立多層繼承'
tags: JS 核心 ,JS , JavaScript
description: 2021/02/19
---

JS 核心 -- 使用 Object.create 建立多層繼承
===

## 原型鏈結構
### :pencil2:  實體可取用物件原型、陣列原型上的方法。 實體繼承在兩者之下

a (陣列實體) 可取用 Array 原型、Object 原型的所有方法

> 原型鏈 : Object 原型 > Array 原型 > a (陣列實體)

```
var a = [];   // a 是一個陣列實體
```

### :pencil2: 使用建構函式建立 Dog 原型，透過他來建立一個建構式，產生 “比比” 的狗

> 原型鏈 : Object 原型 > Dog 原型 > Bibi (實體)

```
function Dog(name, color, size) {
  this.name = name;    // 使用 this 將這些屬性綁定在他們自己身上
  this.color = color;
  this.size = size;
}
var Bibi = new Dog('比比', '棕色', '小');  // 透過 new 運算子產生新物件
```

## Object.create () : 在原型鏈上新增新的層級

### <span class="red">Object.create () 方法 : 可以把其他物件作為原型使用</span>  

> **透過 Object.create() 可以建立一個空物件，同時可以將你帶入 Object.create() 的參數內容變成該物件的原型。**


#### 建立好的狗物件
```
var Bibi = {  
  name: '比比',
  color: '棕色',
  size: '小',
  bark: function () {
    console.log(this.name + ' 吠叫');  // this 指 Bibi 這個物件
  }
}
```

#### 產生新狗　Pupu，透過　Object create ( )方式繼承在 ’比比’ 之下

> 原型鍊的頂點 Object > Bibi 原型 > Pupu 實體

```
var Pupu = Object.create(Bibi);  // 把 Bibi 作為原型使用
console.log(Pupu);        // { }  (目前 Pupu 是空物件，把 Bibi 作為原型使用)
console.log(Pupu.name);   // '比比'

Pupu.name = '噗噗';　　　   // 為 Pupu 這個空物件賦予屬性或方法
console.log(Pupu);        // {name: '噗噗'}  (展開如下圖)
console.log(Pupu.name);   // '噗噗'
```
* Pupu 繼承 Bibi 的一些方法
* 在還沒定義新物件的屬性值前，新物件的屬性值會預設為原物件的屬性值
    * 在不改變屬性的情況下，所有值都可以以 Bibi 做為預設值
* 創造多層原型鏈時，使用 Object.create ( ) 方式  

![](https://i.imgur.com/UmU3iLX.png)

### :pencil2: 範例 - 在原型鏈上新增新的層級

假設今天有一個 Dog 建構式，我們可以用它來建構許多原型為 Dog 的實體。但我們還想在 Dog 和原型鍊的頂點 Object 之間新增一層 Animal 原型。  

> 原型鍊的頂點 Object > Animal 原型(建構式) > Dog 原型(建構式) > Dog 實體  
> 原型鍊的頂點 Object > Animal 原型(建構式) > Cat 原型(建構式) > Cat 實體

(第 12 行)  
1. 把 Animal 的建構函式帶近來，this 指向 animal，後面帶入參數。(call 的第一個參數是指定 this 物件)
2. 物件本身具有傳遞參考的特性
3. 在此的 this 透過 call 傳入後就會使用 new 所產生的 Object。因此執行時，就會將同一個 this 加上 kingdom, family 等屬性
4. new object 在這裡呼叫 Animal, 並指定 Animal 的 this 為自己的 this, 所以 new object 才會具備 animal 與 dog 的屬性   
5. 原本 Dog 的 this 帶入，透過 this 把 Animal 和 Dog 綁在一起，並傳入科別。這裡的 this 是 Dog 與 Animal 共用。


```typescript=
function Animal(family) {   // 創立一個 Animal 建構函式，可以傳入科別
  this.kingdom = '動物界';
  this.family = family || '人科';
}
Animal.prototype.move = function() {  // 在 Animal.prototype 新增方法
  console.log(this.name + ' 移動');
}

function Dog(name, color, size) {
  console.log('this:', this); 
  // 當執行到此行時，this 並不包含犬科。 只有繼承動物界的原型，沒有繼承整個動物界的建構函式
  Animal.call(this, '犬科');  // 加入了 Animal 建構式裡定義的屬性(kingdom, family)
  // (和 18 行呼應)定義科別，透過 call 方法把 Animal 的建構函式帶入
  this.name = name;
  this.color = color || '白色';
  this.size = size || '小';
}
Dog.prototype = Object.create(Animal.prototype);  // Dog 的原型繼承 Animal 的原型
// 只有繼承動物界的原型，但是沒有繼承 Animal 的建構函式 (要使用  Animal.call 帶入)(12 行)
// 因為把狗原型直接繼承在動物界的原型，constructor 會被覆蓋掉，把 constructor 補回
Dog.prototype.constructor = Dog;  // 只有繼承動物界的原型，constructor 被覆蓋要再加回來
Dog.prototype.bark = function () {
  console.log(this.name + ' 吠叫');
}

var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi);  // (如下圖)
Bibi.bark();  // 比比 吠叫
Bibi.move();  // 比比 移動  (繼承於 Animal 的原型下)

function Cat(name, color, size) {
  Animal.call(this, '貓科');
  this.name = name;
  this.color = color || '白色';
  this.size = size || '小';
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function () {
  console.log(this.name + ' 喵喵叫');
}

var Kity = new Cat('凱蒂');
Kity.meow();   // 凱蒂喵喵叫
Kity.move();   // 凱蒂移動
Kity.bark();   // Kity.bark is not a function
```

![](https://i.imgur.com/Alu737b.png)

若沒有 Dog.prototype.constructor = Dog 這一行，我們會發現 Dog 原型裡的 constructor 不見了 (如上左圖)

Dog.prototype = Object.create(Animal.prototype) 把 Dog 的 constructor 覆蓋掉，我們藉著 Dog.prototype.constructor = Dog 把 constructor 補回來。補回來之後的 Bibi (如上右圖)

constructor 補回的用意是讓建構函式的結構(屬性及內容)更為正確。不過實際上，結構不正確只會影響未來的除錯或反查功能，並不會影響運行（但就結構正確性來說，還是建議加上）。

### 小雷點
若把 (22-24 行)和(18 行)交換會跳錯。  
若將 Dog 繼承於 Animal 之下的 create，放在吠叫這個動作的函式下面，如下 :   
```
Dog.prototype.bark = function () {                // (22-24 行)
  console.log(this.name + ' 吠叫');   
}
Dog.prototype = Object.create(Animal.prototype);  // (18 行)
Bibi.bark();   // 跳錯，Bibi.bark is not a function
```
Dog.prototype 會先執行建立賦值 bark，然後後面執行 Object.create(Animal.prototype) 也就是重新賦予值的動作，所以執行 bark( ) 會出現錯誤。




## 什麼是 constructor

使用建構式來產生新物件時，物件原型內的 constructor 就會指向原本的建構函式；正確標示該物件的產生函式。 (constructor 自動產生。)

```
function Animal(family) {   // 創立一個建構函式
  this.kingdom = '動物界';
  this.family = family || '人科';
}
var newAnimal = new Animal('新物種');
console.log(newAnimal);  // (如下圖)
```
![](https://i.imgur.com/ugQN1dp.png)

---

## 原型鏈、建構函式整體結構概念

![](https://i.imgur.com/SP1H1RB.png)

###  ☞ 建立實體的架構表 - 解釋 Bibi 和所有相關的原型之間的關係

![](https://i.imgur.com/JwljlaT.png)

<span class="green">**綠字 : 建構函式**</span>   
<span class="red">**紅線 : 原型鍊架構**</span>   
_ _proto_ _ : 原型鍊 (不斷向上查找)  
constructor : 連結到原本的建構函式  

> 每個原型都有自己的建構方法，每個原型都有自己的 constructor，constructor 顯示原本的建構函式

目前有 Dog、Animal、Object 這些建構函式，透過建構函式就可以來產生他們的原型，使用 Dog.prototype 的方法就可以產生相對應的原型
<span class="green">**( 綠線 )**</span>。   


Q : 為什麼 Dog、Animal、Object 這些建構函式可以使用 Dog.prototype 的方法呢 ?  
A : 因為 Dog、Animal、Object 這些建構函式也有屬於他們自己的原型，同樣都是繼承於 Function 的原型下，連向 Function.prototype <span class="blue">**( 藍線 )**</span>。   

Function 原型也有屬於它自己的建構方式。 (最右方圖示)  
<span class="red">**最頂層的原型物件: Object.prototype**</span>，Object.prototype 是 JavaScript 所有物件的起源。


### :pencil2:  驗證原型鍊的概念 (承上例程式碼)
```
// 以下皆為 true
console.log(Bibi.__proto__ === Dog.prototype);
console.log(Bibi.__proto__.__proto__ === Animal.prototype);
console.log(Bibi.__proto__.__proto__.constructor === Animal);  // 往上二層的建構器是 Animal
console.log(Bibi.__proto__.__proto__.__proto__.__proto__ === null);
```
### :pencil2:  驗證建構函式為什麼可以使用 prototype 的方法
<span class="red">**所有的「建構函式」都繼承於 Function.prototype (函式的原型)**</span>

```
console.log(Dog.__proto__ === Function.prototype);      // true
console.log(Animal.__proto__ === Function.prototype);   // true
console.log(Object.__proto__ === Function.prototype);   // true

console.log(Function.__proto__ === Function.prototype);  // true
console.log(Function.__proto__.__proto__ === Object.prototype);  // true
```



---



## :memo: 學習回顧  
:::info
* <span class="red">所有的建構函式都繼承函式原型，所以 Dog, Animal, Object 這些函式可以使用 .prototype 來建立原型。</span>
    * (Object.__proto__ === Function.prototype);
* (Function.__proto__ === Function.prototype)
* (Function.__proto__.__proto__ === Object.prototype)

:::


<style>
.red {
  color: red;
}
.green {
  color: green;
}
.blue {
  color: blue;
}
</style>