JS原型鏈
這篇文章是「深入ECMA-262-3」系列的一個概覽和摘要。每個部分都包含了對應章節的鏈接,所以你可以閱讀它們以便對其有更深的理解。
對象
ECMAScript做為一個高度抽象的面向對象語言,是通過對象來交互的。即使ECMAScript里邊也有基本類型,但是,當需要的時候,它們也會被轉換成對象。
一個對象就是一個屬性集合,並擁有一個獨立的prototype(原型)對象。這個prototype可以是一個對象或者null。*
讓我們看一個關於對象的基本例子。一個對象的prototype是以內部的[[Prototype]]屬性來引用的。但是,在示意圖里邊我們將會使用__<internal-property>__下划線標記來替代兩個括號,對於prototype對象來說是:__proto__\。
對於以下代碼:

這些prototype有什么用?讓我們以原型鏈(prototype chain)的概念來回答這個問題。
原型鏈
原型對象也是簡單的對象並且可以擁有它們自己的原型。如果一個原型對象的原型是一個非null的引用,那么以此類推,這就叫作原型鏈。
原型鏈是一個用來實現繼承和共享屬性的有限對象鏈。
考慮這么一個情況,我們擁有兩個對象,它們之間只有一小部分不同,其他部分都相同。顯然,對於一個設計良好的系統,我們將會重用相似的功能/代碼,而不是在每個單獨的對象中重復它。在基於類的系統中,這個代碼重用風格叫作類繼承-你把相似的功能放入類A中,然后類B和類C繼承類A,並且擁有它們自己的一些小的額外變動。
ECMAScript中沒有類的概念。但是,代碼重用的風格並沒有太多不同(盡管從某些方面來說比基於類(class-based)的方式要更加靈活)並且通過原型鏈來實現。這種繼承方式叫作委托繼承(delegation based inheritance)(或者,更貼近ECMAScript一些,叫作原型繼承(prototype based inheritance))。
跟例子中的類A,B,C相似,在ECMAScript中你創建對象:a,b,c。於是,對象a中存儲對象b和c中通用的部分。然后b和c只存儲它們自身的額外屬性或者方法。
var a = { x: 10, calculate: function (z) { return this.x + this.y + z } }; var b = { y: 20, __proto__: a }; var c = { y: 30, __proto__: a }; // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80
規則很簡單:如果一個屬性或者一個方法在對象自身中無法找到(也就是對象自身沒有一個那樣的屬性),然后它會嘗試在原型鏈中尋找這個屬性/方法。如果這個屬性在原型中沒有查找到,那么將會查找這個原型的原型,以此類推,遍歷整個原型鏈(當然這在類繼承中也是一樣的,當解析一個繼承的方法的時候-我們遍歷class鏈( class chain))。第一個被查找到的同名屬性/方法會被使用。因此,一個被查找到的屬性叫作繼承屬性。如果在遍歷了整個原型鏈之后還是沒有查找到這個屬性的話,返回undefined值。
注意,繼承方法中所使用的this的值被設置為原始對象,而並不是在其中查找到這個方法的(原型)對象。也就是,在上面的例子中this.y取的是b和c中的值,而不是a中的值。但是,this.x是取的是a中的值,並且又一次通過原型鏈機制完成。
如果沒有明確為一個對象指定原型,那么它將會使用__proto__的默認值-Object.prototype。Object.prototype對象自身也有一個__proto__屬性,這是原型鏈的終點並且值為null。
下一張圖展示了對象a,b,c之間的繼承層級:

注意: ES5標准化了一個實現原型繼承的可選方法,即使用Object.create函數:
var b = Object.create(a, {y: {value: 20}}); var c = Object.create(a, {y: {value: 30}});
你可以在對應的章節獲取到更多關於ES5新API的信息。 ES6標准化了 __proto__屬性,並且可以在對象初始化的時候使用它。
通常情況下需要對象擁有相同或者相似的狀態結構(也就是相同的屬性集合),賦以不同的狀態值。在這個情況下我們可能需要使用構造函數(constructorfunction),其以指定的模式來創造對象。
構造函數
除了以指定模式創建對象之外,構造函數也做了另一個有用的事情-它自動地為新創建的對象設置一個原型對象。這個原型對象存儲在ConstructorFunction.prototype屬性中。
換句話說,我們可以使用構造函數來重寫上一個擁有對象b和對象c的例子。因此,對象a(一個原型對象)的角色由Foo.prototype來扮演:
// a constructor function function Foo(y) { this.y = y; } Foo.prototype.x = 10; Foo.prototype.calculate = function (z) { return this.x + this.y + z; }; var b = new Foo(20); var c = new Foo(30); // 調用繼承方法 b.calculate(30); // 60 c.calculate(40); // 80 console.log( b.__proto__ === Foo.prototype) // true console.log(c.__proto__ === Foo.prototype) // true //同時 Foo.prototype 同時會創建一個特殊的屬性 constructor, b.constructor === Foo // true c.constructor === Foo, // true Foo.prototype.constructor === Foo // true b.calculate === b.__proto__.calculate // true b.__proto__.calculate === Foo.prototype.calculate // true
這個代碼可以表示為如下關系:

正式來說,如果思考一下分類的概念(並且我們已經對Foo進行了分類),那么構造函數和原型對象合在一起可以叫作「類」。實際上,舉個例子,Python的第一級(first-class)動態類(dynamic classes)顯然是以同樣的屬性/方法處理方案來實現的。從這個角度來說,Python中的類就是ECMAScript使用的委托繼承的一個語法糖。
注意: 在ES6中「類」的概念被標准化了,並且實際上以一種構建在構造函數上面的語法糖來實現,就像上面描述的一樣。從這個角度來看原型鏈成為了類繼承的一種具體實現方式:
// ES6 class Foo { constructor(name) { this._name = name; } getName { return this._name; } } class Bar extends Foo { getName { return super.getName + ' Doe'; } } var bar = newBar('John'); console.log(bar.getName); // John Doe
轉自JavaScript. The core.
http://www.yidianzixun.com/n/0CjThYEE?s=9&appid=xiaomi&ver=3.5.5&utk=03smxf0g