---
# System prepended metadata

title: Q1 - Prototype Chain and Class Inheritance

---

# Q1 - Prototype Chain and Class Inheritance 
<br>

Outline :
- 其他語言的繼承（ Classical inheritance ）
- JS 的繼承 ( Prototypal inheritance )
- 原型鏈（Prototype Chain）
- 使用原型繼承的好處
- ES6 中的 class 語法糖

## 前言：簡單了解「其他語言」的繼承

所謂的繼承，簡要而言，就是**一個物件可以將自己的屬性（property）或方法（method），提供給其他物件使用 ; 或者反過來說，一個物件的屬性（property）或方法（method）可以被其他物件提取**。

而提供屬性、方法給其他物件使用的原始物件，可以視為模組，也就是 class。

### Class (類別) 、Instance（實體）與 Classical inheritance

Class (類別) -> 規格書、模組的概念

以製作狗狗為例子，假設想要做各種類型的狗狗，這些狗狗有各自的毛色和體型，但全部都會叫，這時候，最原始的模組 class，可能會長這樣。

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

- 所有的狗都會執行叫這個動作的 func，這類「動態操作」稱之為「方法」。
- 每隻狗有不同的顏色和體型，這類「靜態屬性」稱之為「屬性」。


有了模組 class 後，搭配 new 命令式語法，能做出多種的 instance（實體對象）。

![](https://i.imgur.com/CwkRWmt.png)
( Java 程式。)



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

所以很多隻的狗狗物件有自己的顏色和體型，也都擁有同樣從 class 中提取的叫方法，這樣就是種繼承。

這類型的繼承，就稱為 **classical inheritance**。
<br>

## 從 JS 緣起了解 JS 的原型繼承

Brendan Eich 最初在設計 JavaScript 時，數據類型都是對象 object （參考當時火紅的 object-oriented programming 語言），例如: Java。

But！他並沒有設計 class 的概念，可能是因當時只是想解決一些相對 server 而言不複雜的 client 端問題，不想太複雜或讓語言太困難。

然而還是必須要有一種機制，將所有 object 聯繫起來運作，因此最後還是設計了「 繼承 」的概念。
 
於是參考其他語言後，打算引入 new 命令式來從一個「原型對象（模型）」生成一個「實體對象 (intance)」。

But ! JS 中沒有「 class 」的設計，哪來原型對象？

因此做一個簡化的設計，由於 Java / C++ 等，在使用 new 時，都會調用 class 中的建構函式（constructor）。所以就 **利用 constructor 作為模型對象去 new 出 instance**。

```java=
// 「 Java 」 中， class 中有 constructor 
// 在此其餘語法不是重點，注意第 7 行與第 21 行即可

class Human {
  String name;
  int age;
  int height;
  Human(String str,int a,int b){ // constructor 建構函式初始化用
    name=str;
    age=a;
    height=b;
  }
 
  void eat(){
    System.out.println("eating");
  }
}
//end of class Human

class Test{
  public static void main(String[] args){
    Human h1 = new Human("小木",22,178); // new 時使用 constructor
  }
}
// end of class Test
```


```javascript=
// 「 JS 」中函式可以做為 constructor 使用
// 利用 constructor 生成 instance

function DOG(name){
    this.name = name
    this.species = '犬'
}

const dogA = new DOG('肚子')
const dogB = new DOG('吐司')

console.log(dogA.name) // 肚子
console.log(dogB.name) // 吐司
console.log(dogA) // DOG {name: "肚子", species: "犬"}
console.log(dogB) // DOG {name: "吐司", species: "犬"}


// 無法共享「屬性（Property）」和「方法（Method）」

dogA.species = '貓'
console.log(dogB.species) // 犬 -> 不會受到 dogA 影響，無法共享屬性
```
But ! 用 constructor 生成 instance 是無法共享「屬性」和「方法」。

如此一來，雖然能達成非共享內容的繼承，卻沒辦法達成共享繼承，會導致一些麻煩，例如：記憶體問題、程式撰寫不方便等。

因此 Brendan Eich 決定設計一個 **prototype 屬性**，這個屬性會屬於一個對象（constructor），而所有由這個對象創建的 instance 需要共享 prototype 中的屬性和方法。


```javascript=
// constructor
function DOG(name){ 
    this.name = name;
}

// constructor 中的 prototype 
DOG.prototype = { species : '犬' }

// 生成 instance : dogA & dogB
const dogA = new DOG('肚子')
const dogB = new DOG('吐司')

console.log(dogA) // DOG {name: "肚子"}
console.log(dogB) // DOG {name: "吐司"}
console.log(dogA.species) // 犬
console.log(dogB.species) // 犬
```

- **需要共享**的屬性和方法，就放在 prototype （constructor.prototype）裡，prototype 是物件。
- **不需要共享**的屬性和方法，就放在 constructor 裡，constructor 是物件。
- instance 一創建，會自動引入 prototype 物件中的屬性和方法。

這正是所謂的「**原型繼承 prototypal inheritance**」，prototype 的對象 (DOG) 就像是 instance (dogA / dogB) 的原型，而 instance (dogA / dogB) 像是繼承 prototype 對象 (DOG) 一樣。
<br>

## 所以，DOG.prototype，存在於 instance 的哪裡？

把 instance (dogA / dogB) 印出來看看！找找 DOG.prototype 物件在哪。

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

會發現在 dogA / dogB 中，都有一個特別的 **＿proto＿ 物件**，這個物件中，就存在來自父層級繼承來的 prototype 。

更精確地說法是：**＿proto＿ 會在每個物件被生成時，自動被指派到該物件的屬性上，並會指向該物件的原型物件的 prototype**。

依照上例而言，dogA 物件在被 new 出來時，就有帶有 `＿proto＿` 屬性，並由於 `DOG.prototype = { species : '犬' }` ，所以 dogA.＿proto＿ 就是 `{ species : '犬' }` 。

```javascript=
// 當我們使用 dogA.species 時，會先在自己的物件內尋找，發現只有 name 沒有 species 屬性 ，於是會再自動去尋找 dogA.＿proto＿，發現有 species 屬性於是停止。

console.log(dogA.species) // 犬
console.log(dogA.＿proto＿.species) // 犬
```

![](https://i.imgur.com/2vaHkI3.png)
```javascript=
 DOG.prototype === dogA.＿proto＿ //true
```

## 小結
1. JavaScript 裡沒有 class 模型，是透過 new + constructor 建立 instance 們。
2. instance 們可以透過 constructor.prototype 的方式，共同繼承 prototype 物件中的屬性和方法。
3. ＿proto＿ 存在於每個物件中，指向原型物件的 prototype。當呼叫物件的屬性或方法時，會先尋找物件本身有沒有，沒有的話就會再往物件的  ＿proto＿ 去尋找。

---

## 繼承（Inheritance）
一個物件可以提取到其他物件中的屬性（property）或方法（method）。

繼承可以分成兩種，一種是 classical inheritance，這種方式用在 C# 或 JAVA 當中；另一種則是 JavaScript 所使用的，是屬於 prototypal inheritance。

## 原型鏈（Prototype Chain）
Javascript 使用的是 prototypal inheritance，所以必然會用到原型（prototype）的概念。

- prototype：會一直存在於建構函式（constructor）上的屬性，所有透過該函式產生的物件都有能力存取。
- ＿proto＿：會在物件被生成時一起被指派到物件上的屬性，他決定這個物件的原型物件是誰。

---
## 如果一直往父層的父層的父層的...找下去？
```javascript=
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);
```
**最後都會找到原型鏈最底端的`Object`（原型物件）。**

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

---
Javascript 的純值、物件都有原型鏈，並透過原型鏈繼承屬性、方法：
```javascript=
var a = {};
var b = function() {};
var c = [];
```
![](https://i.imgur.com/6wf5233.png)


---
如果需要的話，不同物件可以分享一樣的原型，它們共享同一個屬性但不是直接的，藉由原型鏈的概念，當 Javascript 搜尋 prop2 時會指向同一個位置。

![原型繼承](https://i.imgur.com/v1KtfeH.png =x300)


舉個例子：
```javascript=
var person = {
  firstname: "Default", // 屬性
  lastname: "Default", // 屬性
  getFullName: function() { // 方法
    return this.firstname + " " + this.lastname;
  }
};
var john = {
  firstname: "John",
  lastname: "Doe"
};
// 將 john proto 物件指向 person
john.__proto__ = person; // john 繼承自 person

// john 裡面找不到所以到 person 找
console.log(john.getFullName()); // John Doe
// 在 john 中找得到的屬性就不會到原型鏈上找
console.log(john.firstname); // John
```
圖示：
![](https://i.imgur.com/jDFqpph.png =x400)

---

## 為何要將屬性和方法建立在原型物件上？
```javascript=
function Person(firstname, lastname) {  
  this.firstname = firstname;
  this.lastname = lastname;  
}
// 新增屬性和方法在 Person 的原型上
Person.prototype.getFullName = function() {
  return this.firstname + " " + this.lastname;
};

var john = new Person("John", "Doe");

console.log(john.getFullName()); // John Doe

```
如果我們將所有的屬性和方法建立在函數中 `Person()`，倘若我們有 1000 個物件都透過 `new` 來建立時，就會有 1000 筆 `getFullName` 存在於記憶體當中，相當佔位置；但若建立在原型物件上，使物件在取用時透過原型鏈找到這屬性和方法，那麼就只需要存在記憶體中一次而不需要在每個物件裡面都建立。

---
## 小結

<!-- ![Prototype chain](https://i.imgur.com/BaYL3qk.png) -->
![Prototype chain](https://i.imgur.com/2YhWlMC.png =x400)


* `prop1`：`obj` 本身就**有** `prop1` 這個屬性，直接取用。
* `prop2`：`obj` 本身**沒有** `prop2`，當找不到時會往該物件的 `＿proto＿` 裡面尋找。
* `prop3`：同理找不到時會往 `＿proto＿` 這個物件裡面的 `＿proto＿` 再找下去。

`prop3` 實際是透過 `obj > ＿proto＿ > ＿proto＿` 這樣往下找的，你可以 `obj.＿proto＿.＿proto＿.prop3` 這樣得到 `prop3`，不過其實只要 `obj.prop3` 即可，而這樣尋找下去的鏈即為原型鏈（prototype chain）。

![demo](https://i.imgur.com/v0L0rn8.png =x200)

> 雖然利用原型繼承相當方便且強大，但必須注意程式碼內原型鏈的長度、必要時打破它們，以避免潛在的效能問題。再來，除非要處理 JavaScript 新語法的相容性，否則絕對不能擴充原生的原型。

---
### JavaScript 中的（偽）Class Inheritance
由於 class 的用法在許多程式語言中都相當普遍，在 ES6 中也會添加了 Class 的用法，然而這種用法實際上和 classical inheritance 仍然是不同的！實際上只是 **syntax sugar**，在 JavaScript 中實踐繼承的方式依然是 prototypal inheritance 的方法。

class 的這種做法在 JavaScript ，只是用來建立物件和原型的另一種**更便利的撰寫方式**，但背後運作的邏輯其實和 function constructor 或者是 Object.create 都還是一樣的。

**可以視為：改用一種 class 的寫法實踐 Prototypal inheritance，這種寫法是抄 Class Inheritance 的寫法。**

```javascript=
///// class 語法糖寫法 /////

// 模組與創建 instance
class Person{
    // 建構函式
    constructor(name){  // 私有
        this.name = name;
    }
    
    // 方法
    getName(){
        return "Hello " + this.name ; // 共有
    }
}

// 建構 john instance
const john = new Person("John"); 
console.log(john); // Person { name: "John" }
console.log(john.＿proto＿);// {constructor: ƒ, getName: ƒ}
```

```javascript=
///// prototype 寫法 /////

function Person(name) { // 私有
    this.name = name;
}

Person.prototype = {
    getName: function(){ // 共有
        return "Hello " + this.name 
    }
}  
 

// 建構 john instance
const john = new Person("John"); 
console.log(john); // Person { name: "John" }
console.log(john.＿proto＿);// {constructor: ƒ, getName: ƒ}
```

好處是「模組間」的繼承很好寫也好理解：

```javascript=
//  code from MDN
class Animal{
    constructor(name){
        this.name = name;
    }

    speak(){
        return this.name + ' makes a bark';
    }
    
    sleap(){
        return this.name + ' makes a sleep';
    }
}

class Dog extends Animal{
    speak(){
        return this.name + ' makes a new bark';
    }
}

const dogA = new Dog("肚子");
console.log(dogA);
dogA.speak(); // 肚子 makes a new bark
dogA.sleap(); // 肚子 makes a sleep
```



 ## 總結

- JavaScrpit 的繼承是藉由 constructor 與 constructor.prototype 實踐
- 原形鏈是物件使用方法或屬性時，會先尋找自身是否有，再尋找 ＿proto＿ 物件是否有，一層一層找上去的過程。
- ES6 的 class 是語法糖



---
參考資料：
- [該來理解 JavaScript 的原型鍊了](https://blog.techbridge.cc/2017/04/22/javascript-prototype/)
- [Javascript继承机制的设计思想](http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html)
- [什么是语法糖？](https://www.zhihu.com/question/20651624)
- [Prototype chain-1](https://blog.techbridge.cc/2017/04/22/javascript-prototype/)
- [Prototype chain-2](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/prototype.html)
- [Prototype chain-3](https://pjchender.blogspot.com/2016/06/javascriptprototypeprototype.html)
- [class inheritance](https://pjchender.blogspot.com/2016/07/javascript-es6classes.html)
- [[筆記] 談談 JavaScript 中的 function constructor 和 prototype 的建立](https://pjchender.blogspot.com/2016/06/javascriptfunction-constructorprototype.html)
- [ [筆記] 談談 JavaScript 中的 function constructor 和關鍵字 new](https://pjchender.blogspot.com/2016/06/javascriptfunction-constructornew.html)
- [[筆記] 談談JavaScript ES6中的Classes](https://pjchender.blogspot.com/2016/07/javascript-es6classes.html)
- [初學者應知道的物件導向 JavaScript](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Object-oriented_JS)
- [Java 備用筆記](https://yubin551.gitbooks.io/java-note/objectConstructor.html)


**報告用投影片**

第一張投影片
![](https://i.imgur.com/hfs5Lmq.png)


第二張投影片
![](https://i.imgur.com/UpQ0sol.png)



