# 原型鏈(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);`會得到 ![](https://i.imgur.com/p47pCPj.png) 印出兩個部分: * `constructor` 這邊就是建構函式的內容物 `Car.prototype.constructor === Car` 會印出true * `__proto__` 在 JavaScript 裡,每個物件型別的變數都有 `__proto__` 印出`console.log(truck.__proto__);` 會得到跟Car.prototype一樣的結果 ![](https://i.imgur.com/VDZK9lD.png) 從這邊可以明白,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 ![](https://i.imgur.com/SkP0n4P.png) ```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),也不要無條件地擴充原生原型,不要使用不要使用原生原型當成變數的初始值,以避免無意間的修改