---
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 ( ) 方式

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

若沒有 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); // (如下圖)
```

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

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

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