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

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

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Peter Chang
Peter Chang

No responses yet