# 原型基礎物件導向

> If you don’t understand prototypes, you don’t understand JavaScript.
>
> 如果你沒搞懂原型，你不算真的懂JavaScript

JavaScript本身就是原型為基礎的物件導向設計，至ES6標準制定後仍沒變動過。在物件的章節中所介紹的類別定義方式，只是原型物件導向語法的語法糖，骨子裡還是原型，並不是真正的以類型為基礎的物件導向設計。理解JavaScript的原型是很重要的，只是混亂得讓初學者難以理解。

> 註: 語法糖(Syntactic sugar)指的是在程式語言中添加的某些語法，這些語法對語言本身的功能並沒有影響，但是能更方便使用，可以讓程式碼更加簡潔，有更高可讀性。另外類似的術語還有"語法糖精"與"語法鹽"。

## 函式 - 原型的起手式

所有JavaScript中的函式都有一個內建的`prototype`屬性，指向一個特殊的prototype物件，prototype物件中也有一個`contructor`屬性，指向原來的函式，互相指來指去會讓你覺得有點怪異，但設計就是如此。

以下的程式碼可以看出這個關係:

```javascript
function Player() { }

console.log(Player)
console.log(Player.prototype)
console.log(Player.prototype.constructor)
console.log(Player.prototype.constructor === Player) //true
```

為了更容易理解，以下是一個簡單的關係圖:

![Prototype Image 01](https://raw.githubusercontent.com/eyesofkids/javascript-entry-level-es6/master/assets/prototype_1.png)

再來是`__proto__`這個內部屬性，它是一個存取器(accessor)屬性，意思是用`getter`和`setter`函式合成出來的屬性，我們可以用它來更加深入理解整個原型的樣貌。`__proto__`是每一個JavaScript中**物件**都有的內部屬性，代表該物件繼承而來的源頭，也就是指向該物件的原型(prototype)，它會用來連接出**原型鏈**，或可以理解為原型的繼承結構。

對於一個函式而言，它本身也是一個物件，它的原型就是最上層的`Function.prototype`，你可以說這是所有函式的發源地。所以`Player`函式本身的`__proto__`指向`Function Prototype`，這應該可以很容易理解。

那麼，`Player.prototype`的`__proto__`指向哪裡？`Player.prototype`本身也是個物件，它指向的就是所有JavaScript中最上層的物件起源，也就是`Object.prototype`。由此也可推知，`Function.prototype`也同樣指向`Object.prototype`。以下面的程式就可以看到這個結果:

```javascript
function Player() { }

console.log(Player.__proto__)
console.log(Player.prototype.__proto__)

//最上層的Object.prototype的__proto__是null值，它是一個特例
console.log(Object.prototype.__proto__)  //null

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

為了更容易理解，以下是一個簡單的關係圖，在圖片中綠色的虛線即是`__proto__`的指向，原本的`prototype`為紅色的實線:

![Prototype Image 02](https://raw.githubusercontent.com/eyesofkids/javascript-entry-level-es6/master/assets/prototype_2.png)

> 註: `__proto__`注意是前後各有兩條下底線(\_)，不是只有一條而已。
>
> 註: `__proto__`在一些舊的瀏覽器品牌(例如IE)中不能使用。雖然在ES6中已經正式納入標準之中，它是個危險的內部屬性，也不要用在真正的應用程式上。

當進一步使用Player函式作為建構函式，產生物件實體時，也就是使用`new`運算符的語句。像下面這樣簡單的例子，在建構函式中會用`this.name`的方式來指定傳入參數，`this`按照之前在物件篇的內容所說明，指向的是`new`運算符中指定的物件實體。

```javascript
function Player(name) {
  this.name = name
}

const newPlayer = new Player('Inori')
```

此時在`newPlayer`物件中的`prototype`與`__proto__`又是如何？由於`newPlayer`是一個物件，並不是函式，它不會有`prototype`這個屬性。`newPlayer`的`__proto__`則是指向`Player.prototype`，把物件的原型鏈整個串接起來。

```javascript
//不是函式，不會有prototype
console.log(newPlayer.prototype)  // undefined

console.log(newPlayer.__proto__)
console.log(newPlayer.__proto__ === Player.prototype) //true
```

以下是一個簡單的關係圖，黃色的代表剛剛實體化的`newPlayer`物件，在圖片中綠色的虛線即是`__proto__`的指向:

![Prototype Image 03](https://raw.githubusercontent.com/eyesofkids/javascript-entry-level-es6/master/assets/prototype_3.png)

最後總結以下的幾個摘要，讓這章節的內容更加清楚:

* 每個函式中都會有`prototype`屬性，指向一個`prototype`物件。例如MyFunc函式的`prototype`屬性，會指向對應的MyFunc.prototype物件。
* 每個函式的`prototype`物件，會有一個`constructor`屬性，指回到這個函式。例如MyFunc.prototype物件的`constructor`屬性，會指向MyFunc函式。
* 每個物件都有一個`__proto__`內部屬性，指向它的繼承而來的原型`prototype`物件
* 由`__proto__`指向連接起來的結構，稱之為原型鏈(prototype chain)，也就是原型繼承的整個連接結構

## 原型真相 - 由問答理解

原型到底是什麼，從上面看到原型鏈、new運算符、建構式等等的概念，你可能會有一些疑惑或誤解。以下是幾個重點，我們用問答的方式來說明。

### 原型是什麼？

一種讓別的物件繼承其中的屬性的物件。

### 任何物件都有其原型？

是的。唯一的例外是Object.prototype(Object的原型)沒有原型，它是所有物件的最上層的源頭。

### 任何物件可以拿來作為原型？

是的。

### 那為什麼上面的例子中，`newPlayer`物件沒有prototype屬性

用`Object.getPrototypeOf`方法或`__proto__`屬性，可以找到這個物件真正的原型是什麼，而這個prototype屬性是給建構函式用的。事實上，每個物件都一定有constructor(建構式)屬性，constructor(建構式)屬性會指向建立這個物件的函式，建構函式的屬性其中就有prototype屬性。

```javascript
const newPlayer = new Player('Inori')

console.log(Object.getPrototypeOf(newPlayer))
console.log(newPlayer.__proto__)

//只能用於不是使用Object.create建立的物件
console.log(newPlayer.constructor.prototype)
```

### 為什麼函式中一定會有prototype屬性，那建構函式與函式的區分又是什麼？

設計上的原則而已，JavaScript並沒有對你所建立的函式，區分建構函式與一般的函式，所以只要是函式，就一定會有prototype屬性(除了語言內建的函式不會有這個屬性)。而且，函式以外的類型，不會有這個屬性。

## 擴充

原型可以很容易的擴充屬性與方法，而且是動態的，可以在物件實體後繼續擴充其中的成員，這也是JavaScript中常用來擴充內建物件的方式。當使用`Player.prototype`來進行擴充時，這些擴充出來的屬性與方法，是所有物件共享的，見以下的範例。

```javascript
function Player(name) {
  this.name = name
}

const newPlayer = new Player('Inori')
console.log(newPlayer.age)

Player.prototype.age = 11
console.log(newPlayer.age)

Player.prototype.toString = function() {
    return 'Name: ' + this.name
}

console.log(newPlayer.toString())
console.log(newPlayer) //觀察物件的內容
```

> 註: 如果以類別為基礎的物件導向，在物件實體化後，物件或類別都是無法擴充其中的成員的。這一點是原型物件導向的彈性之處。

## 繼承

繼承是什麼？我們回歸本質上來思考"繼承的目的是什麼"，在程式開發時的目的通常是為了擴充原有的物件定義不足之處。繼承基本上可以依照不同程式語言的物件導向特性區分為:

* 類別的繼承(Classical inheritance)
* 原型為基礎的繼承(Prototype-based inheritance)

原型繼承是什麼，其實就是原型的擴充語法，上一節有說明過了。至於類別的繼承方式，在JavaScript可以用`Object.create`方法模擬出來，不過複雜多了。

```javascript
//superclass
function Player(name) {
    this.name = name
}

// 1. 呼叫上層的建構式
function VipPlayer(name, level) {
    Player.call(this, name)
    this.level = level
}

// 2. 使用Object.create建立prototype物件。
//    VipPlayer建構式的prototype.constructor為自己。
VipPlayer.prototype = Object.create(Player.prototype)
VipPlayer.prototype.constructor = VipPlayer


var inori = new VipPlayer('inori', 5)

console.log(inori instanceof Player) //true
console.log(inori instanceof VipPlayer) //true
```

相當於ES6中的類別定義方法，使用extends關鍵字來作類別的繼承:

```javascript
class Player{
  constructor (name){
    this.name = name
  }
}

class VipPlayer extends Player {
  constructor (name, level){
    super(name)
    this.level = level
  }
}
```

總而言之，在很多真實的應用情況下，"以合成(或擴充)代替繼承(composition over inheritance)"才是正解，在JavaScript中合成比繼承容易得多了，彈性高應用也很廣，也比較符合語言本身的特性。思考的重點不同，才能撰寫出符合應用情況的程式碼。

## 私有/公開成員

在十多年前Douglas Crockford的這篇[Private Members in JavaScript](http://javascript.crockford.com/private.html)就有提出關於私有成員的樣式，使用的是建構式樣式來模擬私有、公有與特權方法。網路上大部份的文章都是使用這個樣式來說明，或是進一步改良。

不過，私有或公開成員完全不是JavaScript語言中原本就有的設計，用這種方法會限制住只能使用"建構式樣式"的語法來作物件實體化，而且也與原型物件導向概念相去甚遠。原型物件導向對於封裝的概念，基本上是根本沒有。

目前比較簡單常見的區分方式，就是在私有成員(或方法)的名稱前面，加上下底線符號(\_)前綴字，用於區分這是私有的(private)成員，這只是由程式開發者撰寫上的區分差別，與語言本身特性無關，對JavaScript語言來說，成員名稱前有沒有有下底線符號(\_)的，都是視為一樣的變數，至於要如何保護這些私有成員，就靠程式設計者自己了。

## 靜態屬性/靜態方法

JavaScript語言中也沒這概念。不過，原型物件另外定義的屬性與方法，是所有以此原型物件實體化的物件共享的就是了:

```javascript
function Employee() {
}
Employee.prototype.count = 0;

var e = new Employee();
console.log(e.hasOwnProperty('count')); // logs "false"
e.count += 1;
console.log(e.hasOwnProperty('count')); // logs "true"

console.log(e.count);                   // logs "1"
console.log(Employee.prototype.count);  // logs "0"
var e = new Employee();

console.log(e.count);                   // logs "0"
++Employee.prototype.count;
console.log(e.count);                   // logs "1"

var e = new Employee();
console.log(e.count);    // Logs "0", because it's using `Employee.prototype.count`
++e.count;               // Now `e` has its own `count` property
console.log(e.count);    // Logs "1", `e`'s own `count`
delete e.count;          // Now `e` doesn't have a `count` property anymore
console.log(e.count);    // Logs "0", we're back to using `Employee.prototype.count`
```

另一種作法是用IIFE模擬出靜態變數的結構:

```javascript
var Employee = (function() {
    var sharedVariable = 0;

    function Employee() {
        ++sharedVariable;
        console.log("sharedVariable = " + sharedVariable);
    }

    return Employee;
})();

new Employee(); //1
new Employee(); //2
new Employee();  //3
new Employee();  //4
```

## 原型鏈(prototype chain)

原型鏈是JavaScript中以原型為基礎的物件導向特性，指的是使用原型來作物件實體化，會產生"原型鏈"的結構。原型鏈的觀念如此重要，在於有很多物件的行為都是與它相關，在物件篇已經有介紹過物件中的一些方法，都是會遍歷整個物件的原型鏈，而不僅是物件本身而已。這與原型的物件實體化設計有關，因為物件的實體化過程，就是原型的繼承過程，也就是物件的實體化是由繼承其他物件而來的。

先看一下物件屬性的存取這件事，下面的例子中，我們並沒有在`player`物件中定義`toString`方法，但它的確是存在的，這個`toString`方法是來自原型鏈上層的物件中，也就是繼承得來的。

```javascript
const player = {}
console.log(player.toString())
```

### instanceof

`instanceof`是一個運算符，主要是用來判斷一個物件在原型鏈中是否有存在某個建構式，如果存在回傳`true`，要不然就是`false`值。它的前面的運算子是物件，後面則是建構式，語法是像下面這樣:

> object instanceof constructor

`instanceof`常會拿來和存取物件實體的`constructor`屬性的方式作比較，由於`constructor`只會指向最接近的建構式，而`instanceof`會找遍整個原型鏈，當然結果會有所有不同，以下為範例:

```javascript
function Animal(){}

function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat

const kitty = new Cat()


console.log(kitty instanceof Cat) // true
console.log(kitty instanceof Animal) // true
console.log(kitty.constructor === Cat) // true
console.log(kitty.constructor === Animal) // false
```

`instanceof`有一些例外情況，它對於原始資料類型(數字、字串、布林、null、undefined)是無法判斷的，必定是回傳`false`，這和用`constructor`屬性判斷是不同的結果，例如以下的範例:

```javascript
console.log(3 instanceof Number) // false
console.log(true instanceof Boolean) // false
console.log('abc' instanceof Boolean) // false
console.log((3).constructor === Number) // true
console.log(true.constructor === Boolean) // true
console.log('abc'.constructor === String) // true
```

另外對在iframe、frame或著另開視窗中所產生的物件進行判斷時，它也會失效。

在不使用建構式的物件建立的情況，它也無法判斷，而且會產生錯誤，所以它並不能使用於像`Object.create`方法，或直接回傳物件的工廠樣式，只能用於建構式樣式。

### in與for...in

`in`運算符是用來判斷某個屬性是否存在於某個物件中，存在的話會回傳true，否則會回傳false。`in`一樣會尋遍整個原型鏈，對比`hasOwnProperty`則是只會在這物件當中，並不會往原型鏈尋找，`in`通常會搭配for使用`for...in`語句。以下這個語法是最常見到的:

```javascript
for(let key in obj){
    if (obj.hasOwnProperty(key)) {
        console.log(obj[key]);
    }
}
```

### 鴨子類型(Duck typing)

那麼我們要如何正確的判斷一個物件，就是我們需要的物件？首先，`typeof`運算符所能判斷的情況過少，只能用於判斷資料類型，在資料類型那個章節就有提及它的內容。對於物件、陣列、null來說，它都會回傳'object'。

`instaceof`只能用於以`new`實體化的物件，也就是有建構式的情況，而且它有一些失效的情況，`instaceof`有時會直接產生錯誤中斷執行，而不是回傳`false`。`instaceof`並不是不能使用，常見的使用情況是用於判斷語言內建的幾個物件，例如以下幾個:

```javascript
[1, 2, 3] instanceof Array // true
/abc/ instanceof RegExp // true
({}) instanceof Object // true
(function(){}) instanceof Function // true
```

那如果是在一個函式的傳入參數，要如何判斷這個傳入物件實體是我們要的？

答案就是使用"鴨子類型(Duck typing)"，也就是使用該物件的屬性或行為複合地來判斷它，類似像下面的說明:

> 當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子，那麼這隻鳥就可以被稱為鴨子。

所以當我們要判斷一個自訂的物件，可以用其中應該包含的屬性與方法來判斷，例如以下的判斷函式:

```javascript
function isPlayer(object) {
  return object != null && typeof object === 'object' &&
    'name' in object && 'age' in object && 'toString' in object
}
```

## new有害說與物件實體化

JavaScript長期以來就有反對使用new運算符用於實體化物件的言論，建議大家不要使用它來作物件的實體化，主要的理由是語言本身設計上的缺陷:

> 忘了在物件實體化時加上new運算符，函式並無明確區分建立物件用的建構式與一般的函式，如果你是要用來實體化物件，而忘了加上new關鍵字，雖然不會產生任何錯誤，但事情會很大條。

以下用幾個方向來討論如何正確的其他作法，以及如何避免其中可能的問題。

### 防止錯誤的語法樣式

這個樣式可以防止程式設計師少加了`new`運算子。一般的JavaScript函式庫並不鼓勵使用它的程式設計師使用`new`，反而會希望用它的程式設計師都使用函式的方式來建立物件實體，原因除了防止漏寫的錯誤外，在函式也有可能會隱藏對於物件實體的複雜的生成過程。以下為範例:

```javascript
function Player(name, age)
{
   if (this instanceof Player){
      this.name = name
      this.age = age
   }else{
      return new Player(name, age)
   }
}

const aPlayer = Player('Inori', 16)
const bPlayer = new Player('Gi', 16)
```

### Object.create

`Object.create`方法可以使用的是物件的原型來建立物件。這是一個ES5後加入的新方法，最早在10年前在這篇文章[Prototypal Inheritance in JavaScript](http://javascript.crockford.com/prototypal.html)提出的想法與實作。文章中提及`new`本身就是一個為了要讓JavaScript中的物件實體化，用起來像是類別為基礎的程式語言，才會設計的一個語法，但因此模糊了原型繼承的真正作法。

`Object.create`並不只是`new`運算符的取代方法這麼簡單，它提供了更多的彈性，把物件導向的語法結構變成原本的原型導向，它的基本語法如下:

> Object.create(proto\[, propertiesObject])

必要的傳入參數是物件的原型，在下面的範例中可以看到。不過要先說明的是為何它把整個語法結構都轉變了。按照`new`運算符來作實體物件的工作，原本的步驟是像下面這樣的:

1. 先撰寫建構函式，定義好裡面的屬性與方法
2. 然後用`new`來建立物件實體

`new`會作的工作已經說明過很多次了，指向`this`到新建立的物件實體、執行建構函式，最後回傳物件實體。如果你比較一下ES6中的類別語法，這個流程與以類別為基礎的物件導向幾乎無異，差異只是在撰寫類別定義與撰寫建構函式定義上，類別定義現在只是個語法糖，還記得嗎？所以當然是一樣的。

那麼原型物件導向原來的流程應該是如何的？原型物件導向有幾個核心的概念:

* 物件繼承自其他物件: 並不是先有物件的藍圖(類別)，然後實體化它。而是先有要作為原型的物件，然後用由這個物件產生新的物件。
* 以合成(或擴充)代替繼承: 合成(或擴充)的方式才是重點，由原型物件開始實體一個新物件，可以很容易的合成與擴充。

`Object.create`方法相較於`new`運算符，在物件實體化過程中有一個非常明顯的差異:

> Object.create不會執行建構函式

`Object.create`的使用流程會是這樣的，這是簡化過的版本，實際上可能不只這樣:

1. 定義一個物件當作原型物件
2. 從這個原型物件建立另一個新的物件(過程可以加入其他的屬性)

以一個最簡單的範例來說，你可以看到`Object.create`完全不使用建構函式來設定新物件實體的屬性值，只是從原型物件產生一個新物件而已。

```javascript
const PlayerPrototype = {
  name: '',
  toString() {
    return 'Name: '+ this.name
  }
}

const inori = Object.create(PlayerPrototype)
inori.name = 'iNori'
console.log(inori.toString())
```

那麼我們如果要對物件實體進行初始化要怎麼作，其實就是呼叫原型物件裡一個自訂的初始化用的方法就行了，通常會用init來當這個方法的名稱:

```javascript
const PlayerPrototype = {
    init(name, age) {
        this.name   = name
        this.age  = age
    },
    toString() {
        return 'Name: '+ this.name +' Age:'+ this.age
    }
}

const inori = Object.create(PlayerPrototype)
inori.init('iNori', 16)
console.log(inori.toString())
```

> 註: 這個語法樣式，有個專有名稱叫作OLOO(objects linked to other objects)樣式
>
> 註: 因為沒有了建構式，所以有一些屬性(例如constructor)與方法(例如`instanceof`)不能使用，會有錯誤的情況。可以用`isPrototypeOf`方法來判斷原型的關係。

`Object.create`方法的第二個傳入參數，可以使用一種特殊的屬性物件(properties Object)，提供更多在物件建立時的彈性運用，例如以下的範例:

```javascript
const inori = Object.create(PlayerPrototype, {
                hairColor: {
                    value: 'pink',
                    writable: true
                }
              })
inori.init('iNori', 16)

console.log(inori)
```

> 註: 屬性物件是一種用來定義屬性的特殊物件，請參考[Object.defineProperties()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties)

### 建立物件的語法比較

我們目前為止已經看到物件的建立有以下這三種方式:

```javascript
//物件字面定義，相等於new Object()
const newObject = {}

//使用Object.create方法
const newObject = Object.create( proto )

//ES6類別定義，或是建構函式。通常稱為建構式樣式。
const newObject = new ConstructorFunc()
const newObject = new ClassName()
```

在進入建立物件的主題前，我想先說明一下，在JavaScript中關於建立物件這件事，是有需要那麼常用到的嗎？或是真的會在單一個應用程式中，建立大量的物件的情況？

你應該了解，JavaScript的應用程式都是執行在瀏覽器的環境中，這是一個有受到限制的執行環境。而且是當一個使用者連到網頁時，JavaScript的應用程式才會透過網站傳遞到使用者電腦中的瀏覽器，然後才執行，這與一般的桌面或手機上的應用程式，先安裝後才執行完全不同。

一般常見程式設計師會在JavaScript應用程式裡，建立不重覆的自訂物件資料的應用情況有可能是以下幾種:

* 網頁上的UI小元件、行事曆、對話盒等等: 一個網頁上頂多是10-20個物件。
* 用來描述資料模型的物件: 用來作為最終的資料交換使用，描述資料的物件，頂多5\~10個物件。
* 用於應用程式的物件: 例如一個遊戲中，對於怪物、玩家角色、NPC的這些物件，大概就是20-50個。

以效能來說，物件的建立這件事，不太可能像在網路上測試報告的情況，一次建立幾十萬個或百萬個物件。物件的建立與各種運算，本身就是高消費的，所以在物件的建立，反而效率並不是太重要的課題，而是它在撰寫時的高閱讀性、易於維護與擴充、使用的彈性等等。

以上面的三種物件建立的方式來說，效率最佳的是物件字面定義(花括號({})定義物件)，其次為建構函式加上new運算符這種，通常稱之為"建構式樣式(Constructor Pattern)"，最差的則是`Object.create`。

但物件字面定義語法有一些問題，它只會有一個物件實體。它沒辦法直接複製出其他的物件實體，所以如果是要指"可建立多個物件"的語法，這個並不是可以這樣使用的，它需要寫成一個像下面這樣的函式，才能達到需求，這稱之為工廠樣式(Factory Pattern)的語法:

```javascript
function PlayerFactory(name, age) {

  return {
    name : name,
    age: age,
    toString : function() {
        return 'Name: '+ this.name +' Age:'+ this.age
    }
  }
}

const inori = PlayerFactory('Inori', 16)
console.log(inori.toString())

const ayase = PlayerFactory('ayase', 17)
console.log(ayase.toString())
```

單純使用物件字面定義的工廠樣式在效率上是吊車尾的，而且由於所有的物件實體都類似於物件字面所定義出來的，它們的原型都是`Object.prototype`。工廠模式也可以用的`Object.create`方法來建立物件實體，搭配物件字面定義出來的物件，建立新的物件實體，上面已經有範例，要用哪一種，都是要視應用的情況而定的。

### 必要的情況(內建物件)

有些JavaScript語言中的內建物件，在使用時一定要用new運算符進行實體化，例如以下幾個:

```javascript
new Date()
new XMLHttpRequest()
new Error
```

除了這些之外，JavaScript語言中內建的包裝物件幾乎都不使用new作物件實體化，也不建議使用。

### 建構式樣式 vs 工廠樣式

`new`在真實的應用情況也很少會用到，不過我認為主因應該是語法樣式，而非單純只有`new`本身的問題，重點應該放在，對於"建構式樣式"與"工廠樣式"的比較。至少到目前為止的所看到的，"建構式樣式"教得人多，但用得人很少，"工廠樣式"的使用頻率是遠遠勝過"建構式樣式"。

"建構式樣式"是以建構函式式為主的語法樣式，使用new作為物件實體化的唯一方式，最後回傳物件實體。而只能把物件的定義內容寫在建構式之中，只會回傳物件實體，限制住很多能使用的情況。建構函式原本就是一個JavaScript中十分怪異的設計，除了初學者一定很容易和一般的函式搞混，它有一些隱含的機制也很奇特。

"工廠樣式"的語法提供了更多的彈性，相較於"建構式樣式"只能回傳物件，"工廠樣式"最後直接回傳物件實體，但"工廠樣式"也可以多了很多彈性，可以視情況提供各種物件實體的應對程式碼，最後可以回傳以物件字面定義的物件、使用new或Object.create方法。此外，工廠樣式可以對物件資料進行更好的封裝(encapsulation)與資料隱藏(data hiding)，這一點在建構式樣式中完全是個無法比得上的。

### 更多的樣式

#### 原型鏈共享的工廠樣式

工廠樣式提供了更多的彈性，因為`Object.create`直接由一個單純的物件來建立物件，失去了原型鏈的擴充彈性，你可以用下面的樣式來調整:

```javascript
function Player(){}

Player.prototype.toString = function(){
  return this.name
}

function PlayerFactory(name){
  const obj = Object.create(Player.prototype)
  obj.name = name

  return obj
}
```

實際上Player函式與PlayerFactory函式兩者可以合併，像下面這樣:

```javascript
function PlayerFactory(name){
  const obj = Object.create(PlayerFactory.prototype)
  obj.name = name

  return obj
}

PlayerFactory.prototype.toString = function(){
  return this.name
}

const inori = PlayerFactory('Inori')
console.log(inori.toString())
console.log(inori)
```

#### 多重繼承(複合)樣式

這個樣式可以建立繼承自多個物件的新物件，用的是`Object.assign`加上`Object.create`方法的語法，這方式可以一次增加多個新物件的屬性與方法，此外`Object.assign`並沒有限定第二個參數之後只能加一個物件進來合併，所以可以加很多物件來合併成為一個新的物件，類似多重繼承的結果。以下為範例:

```javascript
let player = {
  name: 'player',
  toString() {
   return this.name
  }
}

function PlayerFactory(name, age) {
  return Object.assign(Object.create(player), {
    name: name,
    age: age,
    toString(){
      return 'Name: '+ this.name + ' Age:'+ this.age
    }
  })
}

const inori = PlayerFactory('inori', 16)
console.log(inori.toString())
console.log(inori)
```

#### extend(擴充)樣式

某些時候對於簡單的物件，像是設定值之類的物件，如果都要用到物件實體化或各種語法樣式，實在太過沉重。這個時候用extend(擴充)的方式是快速簡便的，並不一定要額外進行物件實體化，extend(擴充)樣式是一種Mixins(混合)樣式，它並不是繼承或物件實體化的樣式。簡單的extend函式就只是個迴圈語句而已，範例如下(出自[SweetAlert](https://github.com/t4t5/sweetalert)):

```javascript
var extend = function extend(a, b) {
  for (var key in b) {
    if (b.hasOwnProperty(key)) {
      a[key] = b[key];
    }
  }
  return a;
};
```

> 註: 許多JavaScript函式庫例如jQuery、underscore、lodash都有提供extend(擴充)的API，其他也有像clone(複製)、merge(合併)、assign(指定)之類的用於物件的API

## 結語

JavaScript中原型物件導向設計其實並不難理解，難的是它裡面混雜的太多奇奇怪怪的設計，而且又常要與類別為基礎的物件導向設計相比較。本章除了提供原型鏈的基礎知識說明外，也加入了很多你可能會在實際使用時遇到的樣式，工廠樣式與建構式樣式，這兩個基本的樣式你應該要先熟悉。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://eddy-chang.gitbook.io/javascript-start-from-es6/part4/prototype.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
