こんにちは!開発部の上條です。
今回の記事ではJavaScriptのプロトタイプについてご紹介いたします。なるべくわかりやすく記事にまとめたいと思います。それでは、本稿もどうぞよろしくお願いいたします。
目次はこちら
- 1. prototypeは二種類存在する??
- 2. 関数が持つ prototype (アロー関数を除く)
- 3. オブジェクトが持つ [[Prototype]]
- 4. プロトタイプチェーンとは
- 5. プロトタイプは何が良いのか
prototypeは二種類存在する??
JavaScriptのprototypeと表現されるものには二種類あります。
関数が持つ prototype
と
オブジェクトが持つ内部プロパティ [[Prototype]]
です。
この二つのprototypeがJavaScriptのプロトタイプの理解を難しくしています。
関数が持つ prototype
このprototypeは直接参照することが出来ます。
試しにObjectコンストラクタが持つprototypeを見てみましょう。
console.log(Object.prototype);
このprototype
は見ての通りオブジェクトです。
このオブジェクトのプロパティやメソッドはjavascriptが予め用意しているものです。
詳細はMDNのObjectから確認出来ます。
ひとまず、関数にはprototypeというオブジェクトがあることが分かれば大丈夫です。
オブジェクトが持つ [[Prototype]]
インスタンスの生成時、新たに生成されたオブジェクトには、
コンストラクタ関数の prototype
の参照
をもった、内部プロパティ [[Prototype]]
が生成されます。
イメージとしてはこんな感じです。
const newObj = new Object();
↓
newObj.[[Prototype]] = Object.prototype;
この [[prototype]]
は newObj.[[prototype]] という風に参照することは出来ません。
Object.getPrototypeOf()
や __proto__プロパティ
(非推奨) を使用して参照することが出来ます。
const newObj = new Object();
console.log(Object.getPrototypeOf(newObj) === Object.prototype); // true
console.log(newObj.__proto__ === Object.prototype); // true
ここから先はわかりやすさ優先で [[prototype]]
は __proto__
を使って説明していきます。
プロトタイプチェーンとは
プロトタイプチェーンとはオブジェクトのプロパティを参照しようとした時に、
そのプロパティがなければ、オブジェクト.__proto__
にプロパティを探しに行く仕組みです。
更にオブジェクト.__proto__
にもプロパティがなければ、オブジェクト.__proto__.__proto__
に…
というふうに null
になるまで探しに行きます。
例えば
const newObj = {name: '新しいオブジェクト'};
console.log(newObj.toString()); // [object Object]
この例では newObj
には toString()
メソッドは存在していないはずですが、呼び出すことが出来ます。
これは下のような動きをしています。
newObj
にtoString()
メソッドが無いか探す。- 見つからなかったので
newObj.__proto__
つまりObject.prototype
にtoString()
メソッドが無いか探す。 - 見つかった為呼び出す。(newObj.__proto__.toString())
結果 [object Object]
このようにオブジェクトに参照したいプロパティがない場合、コンストラクタのprototypeを探しにいく仕組みをプロトタイプチェーン
と言います。
もし最後まで見つからなかった場合は下のようになります。
const newObj = {name: '新しいオブジェクト'};
console.log(newObj.toArray()); // newObj.toArrayis not a function
newObj
にtoArray()
メソッドが無いか探す。- 見つからなかったので
newObj.__proto__
にtoArray()
メソッドを探す。 - 見つからなかったので
newObj.__proto__.__proto__
に… newObj.__proto__.__proto__
は null なので終了。
結果 newObj.toArrayis not a function
prototypeは拡張することが出来ますのであらかじめtoArrayメソッドを用意していれば下のように実行することが出来ます。
Object.prototype.toArray = function() {
return Object.values(this);
}
const newObj = {name: '新しいオブジェクト'};
console.log(newObj.toArray()); // ["新しいオブジェクト"]
今回の例ではビルトインオブジェクトであるObjectオブジェクトのprototypeを拡張しましたが、基本的にはビルトインオブジェクトを触るのは、よくないこととされています。理由は色々ありますので気になった方は調べてみてください。
プロトタイプは何が良いのか
JavaScriptではオブジェクトのインスタンスは全てコピーとなります。
コンストラクタに直接メソッドを追加すると、生成したインスタンスの数だけメソッドが作成され、メモリの消費が大きくなります。
プロトタイプを使用すれば、暗黙的な参照が持てるのでこの問題を解決することが出来ます。
ちなみに、ES2015からはclass構文が用意されています。そちらを使うとprototypeを使わずに継承を行えます。
今回はプロトタイプの説明でしたので詳しく話しませんが、class構文はプロトタイプベース継承の糖衣構文になります。
是非こちらのclass構文も勉強してモダンなコードもかけるようになりましょう!