Prototype
與 __proto__
咱們先寫下一行代碼:javascript
function Parent {}
當咱們寫下這簡單的一行代碼時,實際上發生了兩件事情java
Parent
prototype
以下圖:segmentfault
構造函數 Parent
中 有一個 prototype
的屬性指向 Parent
的 原型對象 prototype
原型對象 prototype
則有一個 constructor
的屬性 指向回 構造函數 Parent
瀏覽器
緊接着,咱們又寫下一行代碼:函數
var parent = new Parent()
此時,圖片上多出一個新成員this
注意到圖中的 Parent
的實例 parent
裏,有一個[[prototype]]
,爲何這裏不是 __proto__
呢?spa
其實,這裏的 [[prototype]]
表示一種標準規範內置屬性,被一些瀏覽器本身經過__proto__
實現了,對於 Chrome
的實現來講,這個 __proto__
也並不存在於 實例 parent
中,而是 Object.prototype
的一個 存取描述符
,如下代碼能夠證實:prototype
parent.hasOwnProperty('__proto__') // false Object.prototype.hasOwnProperty('__proto__') // true Object.getOwnPropertyDescriptor(Object.prototype, '__proto__') /** * { * configurable: true, * enumerable: false, * get: f __proto__() * set: f __proto__() * } */
咱們之因此能經過 parent.__proto__
訪問到,是由於經過原型鏈訪問到了 Object.prototype
上的 __proto__
存取描述符。設計
如下內容更像是《JavaScript高級程序設計》的筆記,主要提煉出每一個繼承的特色以及例圖。code
function Parent() {} function Child() {} var parent = new Parent() Child.prototype = parent var child = new Child()
此時,根據第一部分所描述的細節,咱們很快能夠畫出這幾行代碼所作的事情:
這樣 child
就能夠經過原型鏈繼承的方式訪問到 parent
以及 Parent.prototype
上的屬性和方法了。 這種方式的特色是:
function Parent(name){ this.name = name } function Child(name){ Parent.call(this, name) } var child1 = new Child('child1') var child2 = new Child('child2')
能夠看到,這種方式和 原型 沒有任何關係,因此畫出的圖也很純粹:
這種方式的特色是:
顧名思義,就是講上述兩種繼承方式有機結合,經過將方法定義在 prototype
中,屬性經過借用構造函數繼承的方式實現繼承。
function Parent(name) { this.name = name } Parent.prototype.talk = function () {} function Child(name) { Parent.call(this, name) } var parent = new Parent('parent') Child.prototype = parent Child.prototype.constructor = Child var child = new Child('child')
此時,關係圖有了一些變化:
咱們能夠從圖中看到,實例 child
和 實例 parent
各自擁有獨立的 namne
,可是共享 Parent.prototype
中的 talk()
方法。這種方式的特色是:
Parent
function createObject(o) { function F() {} F.prototype = o return new F() } function Parent() {} var parent = new Parent() var child = object(parent)
這裏先建立了一個 createObject
函數,其實就是 ES5 Object.create
的模擬實現,將傳入的對象做爲建立的對象的原型。
和 原型鏈繼承
對比一下,咱們發現實際上是同樣的,除了能夠不用建立一個自定義構造函數 Child
。因此特色和 原型鏈繼承
相同:
在 原型式繼承
的基礎上,建立一個僅用於封裝繼承過程的函數,該函數在內部以某種形式來作加強對象,最後返回對象。
function createObject(o) { function F() {} F.prototype = o return new F() } function enhanceObject(o) { var clone = createObject(o) clone.talk = function() {} return clone } function Parent() {} var parent = new Parent() var child = enhanceObject(parent)
經過加強對象,每次建立的新實例,所擁有的方法不是共享 Parent.prototype
中的,而是各自獨立建立的。所以,該方式的特色相似借用構造函數繼承
:
咱們在 組合繼承
中發現,這種方式最大的缺點是會調用兩次父構造函數,
一次是設置子類型實例的原型的時候:
var parent = new Parent('parent') Child.prototype = parent
一次在建立子類型實例的時候:
var child = new Child('child')
回想下 new 的模擬實現,其實在這句中,咱們會執行:
Parent.call(this, name)
因此咱們在例圖中能夠發現,parent
和 child
中都有一份 name
屬性。
所以,經過 在 寄生組合式繼承
中的 createObject
方法,間接的讓 Child.prototype
訪問到 Parent.prototype
,從而減小調用父構造函數的次數。
function createObject(o) { function F() {} F.prototype = o return new F() } function Parent(name) { this.name = name } function Child(name) { Parent.call(this, name) } Child.prototype = createObject(Parent.prototype) Child.prototype.constructor = Child var child = new Child('child')
例圖以下:
這種方式的高效率體現它只調用了一次 Parent 構造函數,而且所以避免了在 Parent.prototype 上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用 instanceof 和 isPrototypeOf。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。
從 Prototype 開始提及 一共分爲兩篇,從兩個角度來說述 JavaScript 原型相關的內容。