JavaScript「prototype」とは?

JavaScriptとは

こんにちは!開発部の上條です。
今回の記事ではJavaScriptのプロトタイプについてご紹介いたします。なるべくわかりやすく記事にまとめたいと思います。それでは、本稿もどうぞよろしくお願いいたします。

目次はこちら

  • 1. prototypeは二種類存在する??
  • 2. 関数が持つ prototype (アロー関数を除く)
  • 3. オブジェクトが持つ [[Prototype]]
  • 4. プロトタイプチェーンとは
  • 5. プロトタイプは何が良いのか

prototypeは二種類存在する??

java-script

JavaScriptのprototypeと表現されるものには二種類あります。
関数が持つ prototype
オブジェクトが持つ内部プロパティ [[Prototype]] です。

この二つのprototypeがJavaScriptのプロトタイプの理解を難しくしています。

関数が持つ prototype

このprototypeは直接参照することが出来ます。
試しにObjectコンストラクタが持つprototypeを見てみましょう。

console.log(Object.prototype);

このprototypeは見ての通りオブジェクトです。

このオブジェクトのプロパティやメソッドはjavascriptが予め用意しているものです。

詳細はMDNのObjectから確認出来ます。

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() メソッドは存在していないはずですが、呼び出すことが出来ます。
これは下のような動きをしています。

  1. newObjtoString() メソッドが無いか探す。
  2. 見つからなかったので newObj.__proto__ つまり Object.prototypetoString() メソッドが無いか探す。
  3. 見つかった為呼び出す。(newObj.__proto__.toString())

結果 [object Object]

このようにオブジェクトに参照したいプロパティがない場合、コンストラクタのprototypeを探しにいく仕組みをプロトタイプチェーンと言います。

もし最後まで見つからなかった場合は下のようになります。

const newObj = {name: '新しいオブジェクト'}; console.log(newObj.toArray()); // newObj.toArrayis not a function
  1. newObjtoArray() メソッドが無いか探す。
  2. 見つからなかったので newObj.__proto__toArray() メソッドを探す。
  3. 見つからなかったので newObj.__proto__.__proto__ に…
  4. 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構文も勉強してモダンなコードもかけるようになりましょう!

jaJapanese