# 原型鏈(Prototype Chain)
###### tags: `Javascript`
> JavaScript 物件是一「包」動態的屬性(也就是它自己的屬性)並擁有一個原型物件的鏈結,當物件試圖存取一個物件的屬性時,其不僅會尋找該物件,也會尋找該物件的原型、原型的原型……直到找到相符合的屬性,或是到達原型鏈的尾端(Object.prototype)。
簡單來說:
當物件想要存取自身沒有的屬性時,他會一直往上找到為止就是原型鏈
# 物件導向(Object-oriented programming;OOP)
物件導向是一種程式設計模式,物件是其中的最基本單位,並且軟體是由無數的物件交互運作而成,而JS就支援這樣的模式並且原型鏈的原理必須從這邊理解
* 類別(class)
擁有 class, instance的概念,class會定義物件的屬性,instance則是由被定義的屬性產生的物件,Java, C++使用類似的概念
* 原型(prototype)
沒有類別跟實體的概念,創立的物件會以原型為範本來繼承屬性,如JS
# 建構函式與實例 Constructor & Instance
Car 其實只是一個普通的函式,但如果你用 new 運算子來呼叫它的話,JavaScript 就會將它視為建構函式。
```javascript=
function Car(wheel, door, fuel) {
this.wheel = wheel,
this.door = door,
this.fuel = fuel
};
let truck = new Car(6, 2, "柴油");
// console.log(truck) 的印出結果
Car {
door: 2
fuel: "柴油"
wheel: 6
__proto__: Object
}
```
你會發現 Car 確實依據我們傳入的參數把 truck 的相關屬性給設定好了,而且在前面標註了 Car,以此說明 truck 是 Car 的實例
# 原型 prototype
> prototype是一個隱藏的內建屬性,在JS中每個函式都會有,而建構函式也是函式,當然就也有 prototype
這邊我們來印出`console.log(Car.prototype);`會得到

印出兩個部分:
* `constructor`
這邊就是建構函式的內容物
`Car.prototype.constructor === Car` 會印出true
* `__proto__`
在 JavaScript 裡,每個物件型別的變數都有 `__proto__`
印出`console.log(truck.__proto__);`
會得到跟Car.prototype一樣的結果

從這邊可以明白,truck作為Car的instance它繼承了Car的屬性,證明方法如下:
```javascript=
console.log(truck.__proto__ === Car.prototype); // true 它們兩個指向同一個物件
```
# new 運算子
> new 背後做的事情不是很複雜但卻很重要,它將instance以及prototype之間建立了連結。
創造instance時會發生:
1. instance會初始化,並可以透過建構函式新增屬性
2. instance的`__proto__`跟建構函式的prototype是一樣的
這邊透過函式使用this設定屬性非常奇怪,this.屬性這樣的方式做添加,不太合理,因為照理來說這樣會加到全域的屬性上面,所以關鍵出在new
其實一切的關鍵都在於 new,我們可以用函式來模擬 new 做的事情:
```javascript=
function newObject(Constructor, arguments) {
var o = new Object(); // 1. 建立新物件
o.__proto__ = Constructor.prototype; // 2. 重新指向原型
Constructor.apply(o, arguments); // 3. 初始化物件
return o; // 4. 回傳新物件
};
let truck = newObject(Car, [6, 2, "柴油"]);
```
把new做的事情拆解出來:
1. 建立物件
2. 把instance的`__proto__`指向Constructor.prototype
3. 初始化物件 利用apply將this指派給instance,因為這樣this才可以添加屬性
4. 回傳新物件
# 原型鏈 prototype chain
new 關鍵字會把instance的`__proto__`指向Constructor.prototype,然而在Consturctor.prototype內卻還有一個`__proto__`
繼續使用上面的範例:
```javascript=
console.log(Car.prototype.__proto__);
```
印出結果會發現:
最後的constructor會指向Object這個constructor

```javascript=
console.log(Car.prototype.__proto__ === Object.prototype); // true
```
更重要的是物件之間的繼承關係,原來是一個接著一個不斷延續的,看起來就像條鎖鏈一樣。
```javascript=
truck.__proto__ === truck.prtotype // Car.prototype
truck.__proto__.__proto__ === truck.prototype.__proto__// Object.prototype
truck.__proto__.__proto__.__proto__ === truck.prototype.prototy.__proto__ // null
```
# 原型 prototype 用法
1. 建構子Book來產出reading_1, reading_2兩個實體
2. 其中有個共用的方法:setComments
3. 對共用的方法實作prototype
4. 在下方就可以直接取用setCommetns的方法
5. 並且兩個的方法確定是同一個函式
將 setComments 這個共用的方法放到 Book.prototype,就不用每次都幫實體建立一份,提出來放到 Book.prototype 也就是原型裡面即可,讓不同的實體reaind1,2都可以讀取到同樣的函式避免記憶體浪費
```javascript=
function Book(name, pNum) {
this.name = name; // 書名
this.pNum = pNum; // 頁數
this.comment = null; // 評等
}
Book.prototype.setComments = function(comment) {
this.comment = comment;
}
var reading_1 = new Book('導讀,型別與文法', 257);
var reading_2 = new Book('範疇與閉包 / this 與物件原型', 251);
reading_1.setComments('好書!');
reading_1.comment // "好書!"
reading_2.setComments('超好書!');
reading_2.comment // "超好書!"
reading_1.setComments === reading_2.setComments // true,確認是同一個函式!
```
## 請勿修改原生原型
建議設定prototype設定在自己創造的函式上,不要修改原生的(例如:String.prototype),也不要無條件地擴充原生原型,不要使用不要使用原生原型當成變數的初始值,以避免無意間的修改