Javascripter 必須知道的繼承 prototype, [[prototype]], __proto__

Peter Chang
13 min readJan 15, 2017

--

__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.comjavascripttutorial.com 是解釋這三者最好的文章,通過簡單的程式碼把[[prototype]], __proto__ prototype 從基礎原理到應用,透過簡單的例子說明,更重要的是有圖片附上(非常重要 :) )。

Object 之間可以互相成為各自的 Prototype,被繼承的 Object 將會繼承父 Object 的 Prototype 所有屬性。[[Prototype]],是一個設定(寫入) Object 的 Prototype 的接口,是一個內部屬性(internal property),它并不允許外部存取。

http://speakingjs.com/es5/ch17.html

下面是說明 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
http://www.javascripttutorial.net/javascript-prototype/

當需要加入 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 中的 xcalculate()

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:

from: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

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 的關系:

https://kenneth-kin-lum.blogspot.tw/2012/10/javascripts-pseudo-classical.html?showComment=1484288337339#c1393503225616140233

通過上圖, 可以看到 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.prototypefoo.__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() { ... };
https://kenneth-kin-lum.blogspot.tw/2012/10/javascripts-pseudo-classical.html?showComment=1484288337339#c1393503225616140233

容易令人混爻的是當開發者看到一系列類似的 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

喜歡這篇文章嗎? 你覺得對其他人有幫助嗎? 請分享給你的朋友和讓我知道你喜歡這類型的文章, 按下面的讚。

--

--