Javascripter 必須知道的繼承 prototype, [[prototype]], __proto__
__proto__ 像動物體內的基因,存在於每一個物件,影響物件的行為和屬性:顏色,足數目,叫聲,是否飛行…等。prototype 像母雞身上的基因,通過 prototype-base-orient 把自身的 prototype 遺傳到下一代小雞身上的基因 __proto__。
Javascript 的繼承自古以來就是一個令人頭痛的問題,沒有一種萬能藥可以解決。要討論繼承必須先討論 Class 的屬性和創建方法,由於 Javascript 是 prototype-base-oriented 的語言,一切的 class 都是假的, class 并不存在。在它的世界裏只有 Object 和 Instance,當中的物體創建,複製或繼承通通是透過 Prototype 完成。
本文章的目的,要弄清 Javascript 中常被混爻的幾個概念 prototype, __proto__, [[prototype]] 和 constructor。文章的內容主要來自2篇很"偉大"的文篇,分別由Dmitry Soshnikov and Kenneth Kin Lum 編寫:
Example 0 : [[prototype]] vs __proto__ vs prototype
speakingjs.com 和 javascripttutorial.com 是解釋這三者最好的文章,通過簡單的程式碼把[[prototype]], __proto__ 和 prototype 從基礎原理到應用,透過簡單的例子說明,更重要的是有圖片附上(非常重要 :) )。
Object 之間可以互相成為各自的 Prototype,被繼承的 Object 將會繼承父 Object 的 Prototype 所有屬性。
[[Prototype]]
,是一個設定(寫入) Object 的 Prototype 的接口,是一個內部屬性(internal property),它并不允許外部存取。
下面是說明 prototype-base (prototypal) 的繼承例子,透過使用內部屬性
[[Prototype]]
,修改 obj 的 Prototype:
var proto = {
describe: function () {
return 'name: '+this.name;
}
};
var obj = {
[[Prototype]]: proto,
name: 'obj'
};> obj.describe
[Function]> obj.describe()
'name: obj'
__proto__
發音 dunder prototype,最先被 Firefox使用,後來在 ES6 被列為Javascript 的標準內建屬性的。它的出現是為了解決讀寫Object.prototype
的麻煩,提供一個快捷讀寫Object.prototype
而設的一個 API,而且它是透過連結內部屬性[[Prototype]]
完成這個功能 (by javascripttutorial)。下面列子說明
b.__proto__
和Foo.prototype
的關系:
function Foo(name) {
this.name = name;
}var b = new Foo('b');
var a = new Foo('a');
b.say = function() {
console.log('Hi from ' + this.whoAmI());
}console.log(a.__proto__ === Foo.prototype); // true
console.log(a.__proto__ === b.__proto__); // true
當需要加入
say()
method 加入到 物件 b,只要把 say() 加入到Foo.prototype
物件,b就會從它身上繼承過來。正如上圖顯示,
b.__proto__
開放接口[[Prototype]]
指向Foo.prototype
物件,把自身的 prototype 從 Foo 物件繼承過來。
Example 1 : Constructor 創造物件
這是從 Dmitry 文章中例子, 展示了 prototype 和 __proto__ 如何在承繼的系統中運作.
Javascript 中的承繼有很多方法, 一些通過 Object.create(), Object.assign()。 最流行的方法是通過 constructor 創造 instance: 自動化設定新建物件 object 的 prototype 屬性, 井把它存放在
ConstructorFunction.prototype
。
創建 Foo 物件和設定 prototype 中的 x 和 calculate()
function Foo(y) {
this.y = y;
}Foo.prototype.x = 10;
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
};
通過 object Foo 的 constructor,創建 instance b:
var b = new Foo(20);b.calculate(30); // 60console.log(
b.__proto__ === Foo.prototype, // true
b.__proto__.calculate === Foo.prototype.calculate // true
b.__proto__.calculate === b.calculate, // true
Foo === b.constructor, // true
Foo === Foo.prototype.constructor, // true
);
正如結果所示, b 物件繼承了 Foo() 的屬性和方法(method). “Foo.prototype” 的內建方法 Foo.prototype.constructor 自動化地為 Foo() 創造構建方法.
Instances “b” 透過 constructor 把自身的 __protot__ 委任(delegation)到 Foo Object 的 prototype:
Example 2 : JavaScript Classical Inheritance diagram
這例子是由另一篇關於 constructor 進行繼承的文章, 由Kenneth的文章取出. 文章把重點放在多重繼承, 下層Object 的 Prototype 和 Constructor 通過往最上層 Object() 的指向而被創造, Object() -> Animal() -> Dog(),這一連串的 constructor 和 prototype 查找關系稱為 prototype chain (by Dmitry).
下圖是關於 JavaScript Pseudo Classical Inheritance. 它表達了 Javascript 引擎中,在物件創件時子類和父類的 Constructor 和 prototype 的關系:
通過上圖, 可以看到 Dog 繼承父物件 Animal 創建的物件, 在 Javascript 的寫法如下:
function Dog() {} // the usual constructor function
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
“new( ) an instance” 到底做了什麼:
即使 Foo.prototype 中的 prototype 被指向到一個存在的 prototype chain,但實際上 Foo.prototype 的 prototype 并沒有被構建(很奇怪吧?)。真正能完成 prototype 的構建是把 __proto__ 指向到一個存在的 prototype chain。
JavaScript’s Pseudo Classical Inheritance 的概念正是如此:我是一個 constructor,我也是一個 function,我身上有著 prototype 的 reference,只要外面隨時呼叫 f00 = new Foo(),我就會將 foo.__proto__ 指向到我的 prototype 物件。
實際上 Foo.prototype 和 foo.__proto__ 是兩個不同的概念:Foo.prototype的意義是,當一個 Foo 的物件被創建,被創建物建 foo 的 foo.__proto__ 會被指向到 Foo.prototype 指向的物建。
如果需要新增 function
如果需要將 move() 這個方法加到 woofie Object,則需要把新方法 move() 加入到它的 prototype chain中,正如一般繼承的使用情景。首先找出 woofie.__proto__,它將會和 Dog.prototype 指向同一個物件。如果 move() 本身不是 prototype chain 的屬性之一(例如 Dog 并沒有這個方法),Javascript 將會往更上一層的 prototype chain 尋找,表達成 woofie.__proto__.__proto__, 或者是 Animal.prototype。
Animal.prototype.move = function() { ... };
容易令人混爻的是當開發者看到一系列類似的 prototype屬性 Constructor.prototype, foo.__proto__, Foo.prototype.constructor.。很有趣的一件事 foo.constructor 并不是 foo 自身的屬性,正如上圖,foo.constructor 將通過 prototype chain 向上查找,結果會指向 Function.constructor (附註 foo.constructor === Foo)。
如果想驗證上圖,可以通過 foo.hasOwnProperty(“constructor”)
Notes:
[[Prototype]]
Object 內部的特殊屬性,用來將物件寫入到 prototype。
__proto__
由ES6 開始成為Object的原生屬性,直接對 [[Prototype]]
進行讀寫。
prototype
是一個Object,當 new 一個 instance 時會被用作指向 __proto__
作為 instance 繼承的屬性。
prototype
只存在於 constructor functions,在 instance 上并不存在。相反__proto__
則出現在所有物件。
( new Foo ).__proto__ === Foo.prototype //true
( new Foo ).prototype === undefined //true
delegate prototypes and concatenative inheritance
Cat.prototype = new Animal();
//it will properly follow the prototype chain through the inheritance hierarchy.Cat.prototype = Animal.prototype
//any runtime changes to the Cat prototype would also affect the Animal
喜歡這篇文章嗎? 你覺得對其他人有幫助嗎? 請分享給你的朋友和讓我知道你喜歡這類型的文章, 按下面的讚。
Reference:
http://speakingjs.com/es5/index.html
http://www.javascripttutorial.net/javascript-prototype/
http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
https://www.quora.com/What-is-the-difference-between-__proto__-and-prototype
http://www.jisaacks.com/prototype-vs-proto/
http://kenneth-kin-lum.blogspot.tw/2012/10/javascripts-pseudo-classical.html