JavaScriptのプロトタイプチェーンを理解するには【開眼JavaScriptまとめ】
目次
今回はJavaScriptの関数のprototypeプロパティについて解説します。prototypeプロパティとは、Function()インスタンスを生成するときにJavaScriptが自動的に付与するオブジェクトです。今回はそんなprototypeプロパティについてまとめます。
開眼JavaScriptとは?
最初に本記事の参考文献である開眼JavaScriptという技術書について軽く説明したいと思います。
開眼JavaScriptの正式なタイトルは”開眼JavaScript 言語仕様から学ぶJavaScriptの本質”といいます。
そのタイトル通りではありますが、JavaScriptの言語特性で知っておくべきポイントを纏めた書籍です。挫折しがちな点である、this、プロトタイプチェーン(継承)、スコープチェーンなどが、サンプルを使って説明されています。
JavaScriptのバージョンはES3を参考に書かれているため、古い仕様ではありますが、JavaScriptの本質的な理解を助けてくれる1冊です。
プロトタイプチェーン
MDNのサイトなどでJavaScriptのリファレンスを参考にするときに、prototypeという表記をよく見ることがあるのではないでしょうか?これがprototypeプロパティになります。
コンストラクタ関数に定義されているprototypeプロパティは、new演算子、もしくはリテラルを使用して生成されたインスタンスオブジェクトからコンストラクタ関数を参照できるリンクを提供します。このときコンストラクタ関数自身がコンストラクタを持っている場合には更に遡るリンクが発生し、通常はObject.prototype
に遡るまで連鎖します。このリンクの連鎖をプロトタイプチェーンと呼びます。
プロトタイプチェーンによって、生成された各インスタンスの間で、「継承」することができるようになります。
実際に目で見てみたほうがわかりやすいと思うので、例として、配列のインスタンスを作成しjoin()
メソッドを呼び出してみます。
const myArray = ["foo", "bar"];
console.log(myArray.join()); // 結果:foo,bar
上記の例では、join()
メソッドはmyArrayインスタンスには定義されていません。しかし、join()
メソッドが定義されているかのようによびだされています。
なぜこのようなことができるかと言うと、join()
メソッドはArray()
コンストラクタのprototypeプロパティに定義されているからです。myArrayという配列オブジェクトのインスタンス(プロトタイプチェーンの終着点はObject.prototypeだとこちらのブログで解説しました)ではjoin()
メソッドが見つからなかったため、JavaScriptはプロトタイプチェーンを遡って、Array.prototypeで発見したjoin()
メソッドを呼び出しました。
すべてのオブジェクトは __proto__ を保持している
以下のコードを実行してみましょう。
const dog = {}
console.log(dog)
生成されたオブジェクトdogの中身をconsole.logで確認すると、__proto__というプロパティが確認できます。
この__proto__
というプロパティは、new演算子、もしくはリテラルを使用してインスタンスオブジェクトを生成するときオブジェクトインスタンスとそのコンストラクタ関数の間に加えられる隠されたリンクになります。
つまり、全ての生成されたオブジェクトは、自動的に proto プロパティを保持しているということです。
JavaScriptはコンストラクタ関数の間にリンクを作成し、このリンクによってプロトタイプが「チェーン」になります。
次の例では__proto__はprototypeと同じものであるということを証明するための例になります。
Array.prototype.foo = "foo"; const myArray = [];
console.log(myArray.proto.foo); // 結果:foo
この結果から、__proto__はprototypeと同じものであるということが分かるかと思います。
オブジェクトの継承におけるプロトタイプチェーン
prototypeプロパティは、伝統的なオブジェクト指向プログラミング言語にみられる継承パターンを模した継承チェーンをJavaScriptでも利用できるようにするために考えられたものです。継承とは、元になるオブジェクト(クラス)の機能を引き継いで、新たなオブジェクト(クラス)を定義する機能のことをいいます。
JavaScriptにおいて、あるオブジェクトが他のオブジェクトから継承する場合にやらなければいけかいことは、継承するprototypeプロパティの値を持つオブジェクトをインスタンス化するだけです。
実際にコードを使って見ていきましょう。
const Animal = function () {};
Animal.prototype = { walk: function () { console.log("トコトコ…"); } }
const Cat = function () { Animal.call(this); }
Cat.prototype = new Animal(); // CatオブジェクトのプロトタイプとしてAnimalオブジェクトのインスタンスをセットしている。 Cat.prototype.meow = function () { console.log("ニャーニャー"); }
const c = new Cat(); c.walk(); // 結果;トコトコ… c.meow(); // 結果;ニャーニャー
この挙動を順を追って説明すると、
- Catオブジェクトのインスタンス c からメンバーの有無を検索する
- 該当するメンバーが存在しない場合には、Catオブジェクトのプロトタイプ、つまり、Animalオブジェクトのインスタンスを検索する
- そこでも目的のメンバーが見つからない場合には、さらにAnimalオブジェクトのプロトタイプを検索する
この例では、Animalオブジェクトでwalkメソッドが見つかるので、検索(プロトタイプチェーン)はそこで終了しますが、見つからない場合には、さらにその上流のprototype(具体的にはObject.prototypeまで)をたどっていくことになります。
このようにJavaScriptでは、プロトタイプにインスタンスを設定することで、インスタンス同士を連結し、互いに継承関係(プロトタイプチェーン)をもたせることができます。
まとめ
今回は、JavaScriptのプロトタイプチェーンを解説しました。みなさんもプロトタイプチェーンを理解して、JavaScriptに対する理解をより深めていってください。