---
# System prepended metadata

title: JavaScript 的物件導向與原型繼承
tags: [WierdJavascript]

---

# JavaScript 的物件導向與原型繼承

## 古典和原型繼承

繼承表示一個物件取用另一個物件屬性或方法，只要了解其簡單觀念就好了，許多人在解釋這區塊時常會用各種火車、汽車等等例子來做舉例，但講師認為直接講清楚會比較簡顯易懂。

那古典繼承和原型繼承是什麼呢?古典繼承在 C#、Java 裡都有，而且非常熱門。

而古典繼承裡面有非常多方法可以用

* friend
* protected
* private
* interface
但我們必須了解他才能夠知道該如何操作。

原型繼承呢?東西就簡單許多了

* 彈性 (flexible)
* 可擴充性 (extensible)
* 簡單易懂 (east to understand)


古典和原型繼承各自都有他的好壞，所以並沒有一定，所以當有人在講繼承時，就是在講

> 一個物件取用另一個物件屬性或方法

## 瞭解原型

### 前言
JavaScript 用了原型繼承，所以代表有個叫作原型（prototype）的概念。

### 瞭解原型
首先我們知道物件可以有屬性和方法，然後我們可以使用點運算子取得屬性或方法，JavaScript 中所有物件、函數，都有原型屬性，而這個屬性會參考到另一個屬性通常被稱為 proto。

假設今天我們有一個 obj 的物件，底下有一個叫 prop1，所以我們可以透過 obj.prop1 來取得。

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

JavaScript 中所有物件、函數，都有原型屬性。

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

而這個屬性會參考到另一個物件，稱之為 proto，而 proto 也可以有屬性，例如 prop2，所以當我們要取用 prop2時就可以這樣寫 obj.prop2。

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

而我們使用點運算子去取用 obj 中的 prop2 時，會找不到，所以他會往原型裡面找。

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

那原型物件也可以指向到另一個物件。

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

每個物件都可以有自己的原型，當這個 proto 裡面有一個 prop3，我們就可以用 obj.prop3 取得。

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

而這過程就像一個鏈子，所以又稱為原型鏈 (prototype chain)，但是不要把他跟範圍鏈搞混了，雖然很相似，可是範圍鏈是尋找可以取用的變數，但原型鏈是尋找屬性及方法。

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

而一般來講這 proto 是隱藏起來的，所以我們才不用這樣撰寫 obj.proto.proto.prop3，只需要 obj.prop3 就好。

但是 JavaScript 中有一個很有趣的狀況，當若有第二個 obj 時，他可以指向同一個原型。

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

所以當我們呼叫 obj2.prop2，他一樣會回傳相同位子。

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

而以上這些就是原型及原型鏈的概念，只需要想簡單一點只是有一個特別的參考到我們的物件而已，那接下來我們直接來看點範例。

```javascript=
var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var john = {
  firstname: 'John',
  lastname: 'Doe',
}
```
這邊我們有兩個物件，接下來我們要將 john 設定成原型，但以下範例千萬不要使用於現實中，這只是為了簡單理解觀念而已。

```javascript=
var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var john = {
  firstname: 'John',
  lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上，這只是為了理解原型而已。
john.__proto__ = person;
console.log(john.getFullName());
```

為什麼不是抓到 Default?因為原型鏈的原因導致，所以點運算子會在 john 裡面找到 firstname，所以就會停下來不會再找了，接下來在加一點物件上去。

```javascript=
var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var john = {
  firstname: 'John',
  lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上，這只是為了理解原型而已。
john.__proto__ = person;
console.log(john.getFullName());

var jane = {
  firstname: 'jane',
}

// 千萬不要使用這種方式在真正的專案開發上，這只是為了理解原型而已。
jane.__proto__ = person;
console.log(jane.lastname);
console.log(jane.getFullName());
```

## 所有東西都是物件(或純值)
### 前言
現在我們知道物件原型，接著我們可以深入瞭解到一件事情 JavaScript 所有東西都是物件或是純值

數值、布林、字串、函數、陣列、物件他們都有原型，除了基本物件(base object)。

### 所有東西都是物件(或純值)
讓我們從範例來瞭解，接下將會利用這三個東西 物件、函數、陣列來講解為什麼所有東西都有原型。

```javascript=
var a = { };
var b = function() { };
var c = [];
```

首先讓我們試著在瀏覽器輸入以上範例，然後再輸入 a.__proto__

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

我們會得到一個基本物件，這在原型鏈上非常底層，而這基本物件有屬性與方法。

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

那函數呢?我們試著輸入 b.__proto__

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

這就是所有函數的原型，所有我們建立的函數都有這個原型，當然也有相關的屬性與方法(你會看到熟悉的 apply、call、bind)。

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

接下來是陣列 c.__proto__

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

這就是一個原型陣列，我們也來看看它是否也有屬性與方法(這裡你也會看到許多熟悉的字眼)。

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

所以由上面這三個範例我們可以知道一件事情。

> JavaScript 所有物件、所有陣列及所有函數都有原型。

那這邊再講一個好玩的問題，原型的原型是什麼?

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

是原型，所以我們要記得，原型鏈最底下的東西就是原型物件。

## Reflection 與 Extend

### 前言
接下來將要講解另一個建立物件的函數很有趣很有效的東西，許多資料庫都會使用到，而這稱為 extend。

而通常我們都是用一個叫 Reflection 的東西來做到 extend。

Reflection: 一個物件可以看到自己的東西，然後改變自己的屬性和方法。

讓我們試著從範例來學習。

```javascript=
var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var john = {
  firstname: 'John',
  lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上，這只是為了理解原型而已。
john.__proto__ = person;

for(var prop in john) {
  console.log(prop + ':' + john[prop]);
}
```

這時候會看到一個很神奇的東西，getFullName()，這是因為 for in 會到外面取用屬性和方法的 不只在物件本身，還會到原型上找

那如果今天我們要

```javascript=
var person = {
  firstname: 'Default',
  lastname: 'Default',
  getFullName: function() {
    return this.firstname + ' ' + this.lastname;
  }
}

var john = {
  firstname: 'John',
  lastname: 'Doe',
}
// 千萬不要使用這種方式在真正的專案開發上，這只是為了理解原型而已。
john.__proto__ = person;

for(var prop in john) {
  if(john.hasOwnProperty(prop)) { // hasOwnProperty 是原型物件的東西，我們可以透過這種方式來確定是否物件有這個屬性。
    console.log(prop + ':' + john[prop]);
  }
}
```

這樣 getFullName() 就沒有出現了，因為 john 的物件屬性並沒有 getFullName()。

而這個概念可以用來幫助我們做一些事情【補足原型繼承】，所以這邊試試看引入 underscore.js。

```javascript=
var newscript = document.createElement('script');
newscript.setAttribute('type','text/javascript');
newscript.setAttribute('src','https://underscorejs.org/underscore-min.js');
var john = {
  firstname: 'John',
  lastname: 'Doe',
}

var jane = {
  address: '111 Main St.',
  getFormFullName: function() {
    return this.lastname + ', ' + this.firstname;
  }
}

var jim = {
  getFirstName: function() {
    return firstname;
  }
}

_.extend(john, jane, jim); // 這樣做將會把物件結合起來
console.log(john);
```

這時候我們可以看到 john 有 jane 的物件地址、jim 物件的 getFirstName 函數，還有 jane 物件的 getFormalFullName()。

那這是怎麼做到的?首先先開啟 underscore.js，開啟之前記得千萬不要害怕開源的專案，這是一個非常好學習教材，我們試著找 extend 看看。

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

首先我們可以看到他建立了屬性或方法，而這個叫 createAssigner 的函數我們試著找看看。

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

我們可以發現 createAssigner 是一個函數，其中他接受了 keys 還有 defaults，然後回傳函數本身，我們可以看到這是一個閉包。

首先它會先取得傳入參數的長度

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

再來是包裝物件，他將物件包裝起來(避免參考問題) (((我猜)))

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

接下來就是當物件長度若小於兩筆，或是物件為空，就返回物件

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

接下來他會用 for 將所有物件跑一遍，因為陣列第一筆是 john ，所以起始會直接跳過 0 從 1 開始

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

所以因為經過 underscore.js 的處理， jim 就可以使用 getFormFullName()了。

```javascript=
var newscript = document.createElement('script');
newscript.setAttribute('type','text/javascript');
newscript.setAttribute('src','https://underscorejs.org/underscore-min.js');
var john = {
  firstname: 'John',
  lastname: 'Doe',
}

var jane = {
  address: '111 Main St.',
  getFormFullName: function() {
    return this.lastname + ', ' + this.firstname;
  }
}

var jim = {
  getFirstName: function() {
    return firstname;
  }
}

_.extend(john, jane, jim); // 這樣做將會把物件結合起來
console.log(john);

john.getFormFullName();
```

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


###### tags: `WierdJavascript`