原文地址:Javascript – How Prototypal Inheritance really worksjavascript
在網上能夠看到各類關於Javascript原型繼承的文章,但Javascript規範中只提供了new操做符這一種實現原型繼承的方法。所以網上大多數的文章是具備迷惑性的,很混亂。這篇文章會讓你清晰的認識到什麼是真正的原型繼承?而且怎麼樣使用它?html
你會常常看到以下關於原型繼承的定義:java
訪問一個對象屬性的時候,Javascript會沿着原型鏈向上尋找,直到找到該屬性。app
Javascript中大多數實現方式都是使用__proto__來指定原型鏈中下一個被訪問的對象,接下來會揭示__proto__與prototype之間的區別。函數
注意:不要在你的代碼中使用__proto__,文中使用它僅僅是爲了更好的解釋Javascript繼承是如何工做的。優化
下面的代碼展現了Javascript引擎如何檢索對象的屬性(僞代碼,僅爲了方便理解)this
function getProperty(obj, prop) { if (obj.hasOwnProperty(prop)){ return obj[prop]; }else if (obj.__proto__ !== null){ return getProperty(obj.__proto__, prop); }else{ return undefined; } }
舉個例子:一個二維的點。擁有x座標屬性,y座標屬性和一個print方法。spa
用書面語言來表示該定義就是:咱們定義了一個有三個屬性的對象:x,y和print。爲了構造一個新點,咱們只須要建立一個對象並將他的__proto__屬性指向Point。prototype
var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = {x: 10, y: 20, __proto__: Point}; p.print(); // 10 20
怪異之處在於解釋原型繼承的人給出的例子每每與他們的定義不相符合,他們給出的代碼一般以下所示:code
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); p.print(); // 10 20
上面所例舉的代碼跟原型繼承徹底不相關,Point是構造函數,它有一個prototype屬性,使用了new操做符,可是而後呢?
Brendan Eich 想讓javascript像Java,C++這些傳統的面嚮對象語言同樣,用new操做類直接構造一個實例,因此他給Javascript也添了new操做符。
C++中有構造函數,用來初始化實例的屬性。所以,new操做符操做的對象必須是函數。
咱們須要把對象的方法掛載到某個地方,因爲咱們使用的是原型語言,咱們把他放在函數的原型屬性裏。
構造一個類的實例:這個實例是一個空對象,而且他的__proto__屬性指向構造函數的原型。
初始化實例:構造函數被調用,並將this指向這個實例。
返回實例對象。
如今咱們瞭解了new構造的過程,咱們在Javascript中實現它:
function New (f) { var n = { '__proto__': f.prototype }; return function () { f.apply(n, arguments); return n; }; }
舉一個小例子:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { print: function () { console.log(this.x, this.y); } }; var p1 = new Point(10, 20); p1.print(); // 10 20 console.log(p1 instanceof Point); // true var p2 = New (Point)(10, 20); p2.print(); // 10 20 console.log(p2 instanceof Point); // true
Javascript規範只定義了new操做符的工做流程,Douglas Crockford發現了一種利用new實現原型繼承的新方法,他寫的Object.create函數。
Object.create = function (parent) { function F() {} F.prototype = parent; return new F(); };
看起來很奇怪,但實際上很簡潔。他只建立了一個新的對象,原型你能夠隨意設置。若是容許使用__proto__的話,這個例子能夠這樣寫:
Object.create = function (parent) { return { '__proto__': parent }; };
下面這個Point例子纔是真正的原型繼承。
var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = Object.create(Point); p.x = 10; p.y = 20; p.print(); // 10 20
咱們瞭解了原型繼承並用一種具體的方法實現了它。但這樣寫有一些缺點:
不標準:__proto__不是標準並不同意使用, 而且原生的 Object.create 和 Douglas Crockford的實現不等同。
不優化:Object.create (原生的或自定義的)做爲構造函數是及其不高效的。