ECMAScript 2015 で class 定義が導入されましたが、これはシンタックスシュガーであり、javascript はプロトタイプベースです。
とはいっても class 定義で実装する機会が増えるでしょうし、TypeScript とか使うとさらにプロトタイプを意識することが少なくなりそうな気がしています。
とはいっても javascript を使用する以上、プロトタイプは頻出するので、「プロトタイプ??」っていう人向けに基本のおさらいです。
頻出用語
javascript プロトタイプを理解する上で、頻出する用語と、その用語の端的な説明です。
グローバルオブジェクト
ブラウザー内では window オブジェクトが、グローバルオブジェクトです。グローバル変数や関数はすべて、 window オブジェクトのプロパティとしてアクセスすることができます。
1 2 3 4 |
// 変数 hoge をグローバルに宣言 var hoge = 'hogehoge' // window オブジェクトのプロパティ: hoge と同一 hoge === window.hoge -> true |
Object
javascript のすべてのオブジェクトは Object に由来します。
Function Object
function 宣言で作成された関数は Function オブジェクトであり、Function オブジェクトの全てのプロパティ、メソッド、振る舞いを持ちます。
1 2 3 4 5 6 7 8 9 |
// function 式で生成されるオブジェクトは Function から生成された Function オブジェクト // 次の二つはどちらも Function オブジェクト となる function calcRectArea(width, height) { return width * height; } function Student(name, age) { this.name = name; this.age = age; } |
javascript では、function(関数)は Function オブジェクトと呼び、オブジェクトとして扱われます。
コンストラクタ
javascript プロトタイプを理解を妨げる紛らわしいものの1つであるコンストラクタについてです。コンストラクタがつく用語が複数あるので整理します。
コンストラクタ関数
オブジェクトを作成および初期化する関数オブジェクトです。Java や C# でいうコンストラクタと同義です。
1 2 3 |
// 下記の function 宣言している Student(name, age)がコンストラクタ関数 function Student(name, age){ this.name = name; this.age = age; } var student = new Student('ken', 18); |
Object コンストラクター
Object コンストラクターは、%Object%組み込みオブジェクトであり、グローバルオブジェクトのObjectプロパティの初期値です。
1 2 |
// window オブジェクトの初期プロパティとして存在 window.Object |
Function コンストラクター
Function コンストラクターは、%Function%組み込みオブジェクトであり、グローバルオブジェクトの Function プロパティの初期値です。
1 2 3 4 5 6 |
// window オブジェクトの初期プロパティとして存在 window.Function // そのため、new Function で記述可能 var sum = new Function('a', 'b', 'return a + b'); console.log(sum(2, 6)); // expected output: 8 |
コンストラクタ関数と関数宣言の違い
Function コンストラクタによる関数の生成は、生成コンテキストにクロージャを作りません。つまり常にグローバルスコープで作成します。
constructorプロパティ
オブジェクトのコンストラクタ関数への参照です。
1 2 |
// 先ほど生成した Student(name, age) のコンストラクタプロパティを見る student.constructor -> ƒ Student(name, age){ this.name = name; this.age = age; } |
class 定義のコンストラクタメソッド
prototype と __proto__([[Prototype]])の違い
まず最初に、Chrome DevTools で適当にオブジェクトを作成します。すると、__proto__ という奇妙なプロパティが現れます。
1 2 |
// 適当に変数を作成 var obj = { name: 'taro' } |

次に someObject.[[Prototype]] です。これは記法で、 someObject のプロトタイプを示します。つまり JavaScript の __proto__ プロパティ(現在は非推奨)と同等(someObject.[[Prototype]] = __proto__)です。それでは、__proto__ が一体何者なのか見ていきましょう。
__proto__
ECMAScript 2015 の仕様を見ます。
All ordinary objects have an internal slot called [[Prototype]]. The value of this internal slot is either null or an object and is used for implementing inheritance. Data properties of the [[Prototype]] object are inherited (are visible as properties of the child object) for the purposes of get access, but not for set access. Accessor properties are inherited for both get access and set access.
機械翻訳:
すべての通常のオブジェクトには、[[Prototype]]と呼ばれる内部スロットがあります。この内部スロットの値はnullまたはオブジェクトのいずれかであり、継承の実装に使用されます。 [[Prototype]]オブジェクトのデータプロパティは、取得アクセスの目的で継承されます(子オブジェクトのプロパティとして表示されます)が、セットアクセスではありません。アクセサプロパティは、取得アクセスと設定アクセスの両方で継承されます。
要約すると、「someObject.[[Prototype]] = __proto__」であること、「__proto__ は、継承元の prototype(後続で説明) 」である。
ここで、少しでもプログラミング経験があれば、お馴染みの「継承」という用語が登場しました。javascript では、この継承(prototype)のつながりのことを、プロトタイプチェーンと呼びます。
次は「prototype」です。ここで注意点が1つあります。ECMAScript 2015 から、__proto__([[Prototype]]) は、 Object.getPrototypeOf() と Object.setPrototypeOf() のアクセサを使ってアクセスします(現在 __proto__ プロパティの直アクセスは非推奨です)。但し、説明コードは可読性を考慮して__proto__で記載します。
プロトタイプ(prototype)
他のオブジェクトの共有プロパティを提供するオブジェクトです。
1 2 3 4 5 6 |
// Object には、is, create メソッドがある Object.is | create // 適当なオブジェクトを生成する var obj = { name: 'taro' } // obj の継承元を確認する obj.__proto__ === Object.prototype -> true |
Object.prototype には、is, create メソッドはありません。継承元にないので、当然、 obj にも存在しません。


プロトタイプとコンストラクタ
各コンストラクターは、プロトタイプベースの継承と共有プロパティの実装に使用される prototype プロパティを持つ関数です。

プロトタイプチェーン
Object プロトタイプチェーン
継承された toString()
この toString() メソッドは誰のメソッドでしょうか?
1 2 3 4 5 |
// リテラル表記でオブジェクトを生成 var o = { name: 'ken', age: 18} // toString()を実行可能だが、見ての通り toString は実装していない o.toString() |
1 2 3 4 5 6 7 8 9 10 11 |
// toString を持っているか console.log(o.hasOwnProperty('toString')); -> false // o の継承元を調べる o.__proto__ === Object.prototype -> true // toString を持っているか console.log(Object.prototype.hasOwnProperty('toString')); -> true // toString が Object の toString か調べる o.__proto__.toString === Object.prototype.toString -> true |
プロトタイプチェーンの末尾
1 2 |
// 継承元となる Object.prototype.__proto__は? Object.prototype.__proto__ -> null |
Object は 実は Function から生成
1 2 3 4 5 |
// Object にも、__proto__ が存在する Object.__proto__ // Object.__proto__ は誰か Object.__proto__ === Function.prototype -> true |
どこでチェーンが切れるか
1 2 3 4 5 6 7 8 9 10 |
Object.__proto__ === Function.prototype -> true Object.__proto__.__proto__ === Object.prototype -> true // プロトタイプチェーンの末尾 Object.__proto__.__proto__.__proto__ === null -> true // Object.__proto__ === Function.prototype -> true ですが、実は下も true Function.__proto__ === Function.prototype -> true // Function ⇒ Object 生成 // Function ⇒ Function 生成 |
まとめ
prototype と __proto__ の正体は分かってもらえたでしょうか?感のするどい人は、「存在しないプロパティへのアクセスは、プロトタイプチェーン全体を通過する⇒パフォーマンス低下」みたいなことにも気づいたかもしれませんね。もっと prototype について知りたいと思った方は、MDN を見てください。とても勉強になりますよ。
ただ、個人的には冒頭にも書きましたが、パフォーマンスやメモリに気を付けながら ECMAScrip 2015~、TypeScript のクラス定義で実装すると思います。babel や webpack を上手く使えば、レガシーブラウザでも動作しますしね。