prototype や __proto__ それぞれを詳しく説明しているものは存在しますが、この2つの違いを明確にまとめている情報があまりありませんでした。
そこでプロトタイプベースとはどういうことなのか、コード上に登場する prototype や __proto__ とは一体何なのかを自分なりに整理してみました。
- 更新履歴
-
- [2017.06.03] コンテンツ修正しました。sititou70様、ご指摘いただき有難うございました。
- [2014.07.19] コンテンツ修正しました。通りすがり様、ご指摘いただき有難うございました。
目次
プロトタイプ
JavaScript は「プロトタイプベース」のオブジェクト指向言語です。
まず最初にこの「プロトタイプベース」のオブジェクト指向とはいったい何なのか、クラスベースのオブジェクト指向と比較してみました。
- クラスベース
- オブジェクトの雛形として必ずクラスが存在し、クラスをインスタンス化することでオブジェクトが生成される。
- プロトタイプベース
- オブジェクトは既存のオブジェクトを元(プロトタイプ)とし、ユニークな特徴を付加することで存在する。
JavaScript はクラスを定義しなくても、新しいオブジェクトを生成したときには、すでに別のオブジェクトを元に生成されているということなのですね。
そして、この元こそがプロトタイプであると言うことがわかります。
プロトタイプ と [[Prototype]] と prototype
プロトタイプ は仕様上の 概念 であって、実装時に定義したり参照できるものではありません。その概念を [[Prototype]] 内部プロパティである prototype や __proto__ により実現されています。
よって、全てのオブジェクトは内部プロパティ[[Prototype]]を持つということになります。
prototypeとは
JavaScript の function 式で生成されるオブジェクトには prototype というプロパティを持っています。これがプロトタイプの正体です。
全てのオブジェクトが持つプロパティfunction式で生成されるオブジェクトが持つプロパティ(※1)- prototype プロパティには何らかのオブジェクトが格納されている
※1 [2014.07.13](修正) prototypeは関数が生成されるときに自動的に作られ、{constructor:ownFunction}が入れられるプロパティで、普通のオブジェクトは持ちません。
次のサンプルコードでは Sample という関数(コンストラクタ)を定義し、Sample の prototype へ a プロパティを定義し、そこへ文字列 a を代入しています。
サンプルコード
[code]
function Sample() {}
Sample.prototype.a = ‘a’;
var sample_obj = new Sample();
console.log(sample_obj.a);
[/code]
結果
[code]
a
[/code]
Sample のインスタンスである sample_obj 生成し、インスタンスから a プロパティにアクセスすると、prototype で定義した a の値が暗黙の参照により出力されます。
prototype(プロトタイプ)まとめ
プロトタイプは概念上の言葉で、 prototype というオブジェクトのプロパティにより実現されている。
プロトタイプチェーン
JavaScript がオブジェクト指向言語でありながら、クラスベースの言語と比較して、オブジェクトの型を意識しなくても何となく動いてしまう理由は、インスタンスが暗黙の参照により、オブジェクトが持つプロパティ prototype 順に辿っていくためです。(※2)このことをプロトタイプチェーンと呼びます。
※2 [2014.07.19](加筆) チェーンが辿るのは [[Prototype]] であり、親の prototype プロパティとは直接関係ありませんが、new演算子が親の prototype プロパティを子の [[Prototype]] に設定するようになっているため、全てのオブジェクトはどんな名前のプロパティであっても [[Prototype]] に適応され得ます。
プロトタイプチェーンについては以下のサンプルコードをみれば分かりやすいと思います。
サンプルコード
[code]
function SampleA() {}
SampleA.prototype.a = ‘a’;
var SampleB = function() {}
SampleB.prototype = new SampleA();
var sample_obj = new SampleB();
console.log(sample_obj.a);
[/code]
結果
[code]
a
[/code]
SampleA という関数(コンストラクタ)を定義し、SampleA の prototype に対し a というプロパティを定義し、文字列 a を代入しています。
続いて SampleB を定義、その prototype に SampleA のインスタンスを代入します。
最後に SampleB のインスタンス sample を生成し、インスタンスから SampleA で定義した a プロパティにアクセスすると 文字列 a が出力されました。
この例ではプロトタイプチェーンにより、JavaScript でもクラスベース言語のように SampleB が SampleA を継承していることが分かります。
プロトタイプは次のような経路で探索していきます。
プロトタイプチェーンの経路
- 現在のインスタンスのプロパティを調べる
- インスタンス元のオブジェクトのプロトタイプを調べる(上位のオブジェクトがある場合は継承の頂点まで繰り返す)
- 継承ツリーの頂点 Object.prototype を調べる
- Object.prototype で見つからない場合は undefined を返す(※3)
サンプルコードでは以下の順にプロトタイプの探索が行われています。
- sample_obj の prototype にプロパティ a があるか調べる -> なし
- SampleB の prototype にプロパティ a があるか調べる -> なし
- SampleA の prototype にプロパティ a があるか調べる -> あり(探索終了)
プロトタイプチェーンまとめ
prototype に定義したプロパティは、インスタンスから暗黙の参照により、継承ツリーの頂点である Object.prototype までを順に探索する。(※3)
※3 [2014.07.19](加筆) チェーンの末端は Object.prototype と決まっているわけではなく、厳密に言えば [[Prototype]] が null のときに探索が終了します。
prototype と __proto__
ここまででプロトタイプが仕様上の概念であること、prototype は関数オブジェクトのもつプロパティであることは理解出来ました。
それでは一体 __proto__ とは何でしょうか。
__proto__とは
全てのオブジェクトが持つプロパティObject.prototypeから継承されるアクセサメソッド(※4)- __proto__ の値が null になるまでプロパティを探索していく
- Object.prototype の __proto__ は null
- new でインスタンスを生成したとき、関数(コンストラクタ)の prototype へのアドレスを格納する
※4 [2014.07.13]修正 __proto__ は Object.prototypeをチェーンに持たないオブジェクトでは発現しません。
一見 prototype との違いが分かりません。これを見る限り prototype と __proto__ は同じものとして扱っても問題ないと思います。
しかし、4番目の特徴をみると __proto__ の本当の姿が見えてきます。
以下のサンプルコードは、先ほどのプロトタイプチェーンのサンプルコードに、__proto__ のログ出力を追加しただけのものです。
サンプルコード
[code]
function SampleA() {}
SampleA.prototype.a = ‘a’;
console.log( SampleA.prototype.__proto__ );
var SampleB = function() {}
SampleB.prototype = new SampleA();
console.log( SampleB.prototype.__proto__ );
var sample_obj = new SampleB();
console.log( sample_obj.__proto__ );
console.log( sample_obj.__proto__.a );
[/code]
結果
[2017.06.03](修正)
[code]
Object.prototype
SampleA.prototype
SampleB.prototype
a
[/code]
SampleA の prototype の __proto__ は Object.prototype
SampleB の prototype の __proto__ は SampleA.prototype (または new SampleA()).__proto__)
インスタンス sample_obj の __proto__ は SampleB.prototype (または new SampleA())
インスタンス sample_obj の __proto__ のプロパティ a は 文字列 a
がログ出力されていることが確認できます。
このことから、上位の prototype のアドレスを、それぞれのオブジェクトの __proto__ に自動で代入していることが分かります。
プロトタイプチェーンは prototype の __proto__ が参照しているアドレスを順に探索しているのです。
__proto__まとめ
__proto__ には prototype のアドレスが格納されている。prototype は __proto__ のアドレスを参照し、プロトタイプチェーンが実現している。
まとめ
プロトタイプ
- 仕様上の概念や仕組みそのものを指す
- 全てのオブジェクトは内部プロパティ [[Prototype]] を持つ
prototype
すべてのオブジェクトが持つプロパティfunction式で生成されるオブジェクトが持つプロパティ- 最上位の Object.prototype までプロパティを探索する
- prototype プロパティには何らかのオブジェクトが格納されている
__proto__
すべてのオブジェクトが持つプロパティObject.prototypeから継承されるアクセサメソッド- 最上位の Object.prototype.__proto__ までプロパティを探索する
- Object.prototype の __proto__ は null
- new でインスタンスを生成したとき、関数(コンストラクタ)の prototype へのアドレスを格納する
JavaScript はプロトタイプベースのオブジェクト指向言語ですが、クラスやインスタンスという概念が存在しないわけではありません。
ここでも少し触れましたが、JavaScript でクラスを作成する方法は こちら を参考にしてみてください。
この記事がみなさんのお役に立ちましたら、下記「Share it」よりブックマークやSNSで共有していただければ幸いです。