Javascript

【JavaScript】プロトタイプチェーンについて理解をまとめる

こんにちは、ともです。

この記事ではプロトタイプチェーンについて理解をまとめておこうとと思います。

ES6以前のJavaScriptでのオブジェクトの継承にはプロトタイプチェーンという考えが存在します。

ES6からのclass構文はプロトタイプチェーンの糖衣構文なのでやはりプロトタイプチェーンの理解は必須だと感じます。

この記事ではプロトタイプチェーンを利用したオブジェクトの継承について理解をまとめておこうと思います。

プロトタイプとは

JavaScriptはプロトタイプオブジェクトを利用しオブジェクトにメソッドを定義します。

JavaScriptではオブジェクトのインスタンスは全てコピーとなります。

よって、オブジェクトにメソッドを記載した場合、生成したインスタンスの数だけメソッドが存在することになります。これではメモリ消費が大きくなります。

プロトタイプとしてメソッドを定義した場合、生成したインスタンスからオブジェクトに暗黙的参照が作られるため、この問題を解決することができるのです。

詳しくは下記の記事にまとめました。

プロトタイプチェーンとは

プロトタイプチェーンとはJavaScriptにおける継承の仕組みです。

JavaScript のオブジェクトはプロパティ(自身のプロパティを指す)の動的な「かばん」であり、プロトタイプオブジェクトへの繋がりを持っています。あるオブジェクトのプロパティにアクセスを試みるとき、そのオブジェクトのみならず、そのオブジェクトのプロトタイプ、プロトタイプのプロトタイプ…と、一致する名前のプロパティが見つかるか、プロトタイプチェーンの終端に到達するまで、そのプロパティが捜索されます。

継承とプロトタイプチェーン|MDN

JavaScriptにおいて、オブジェクトとオブジェクト間には繋がりを定義することが可能であることが分かります。これがプロトタイプチェーンであり、JavaScriptの継承の仕組みであります。

プロトタイプチェーンの例

var Animal = function Animal() { }
var Dog = function Dog() { }

Animal.prototype = {
  walk: function () { console.log('トコトコ') }
}

// Animal->Dogの継承
Dog.prototype = new Animal()

var dog = new Dog();
dog.walk();

Animalを継承したDogを作成してみます。

プロトタイプチェーンの方法はプロトタイプオブジェクトに継承ものとインスタンスをセットするとこで実現されます。Dog.prototype = new Animal()

これにより継承元のAnimalで実装していたwalkメソッドがDogのインスタンスから利用することができます。

ステップを辿ると上記の流れです。

  1. 自身のインスタンスにwalkが無いか確認→無い
  2. Dogオブジェクトにwalkが無いか確認→無い
  3. Dogのprototype先(継承元)を確認→Animal
  4. Animalオブジェクトにwalkが無いか確認→有る

ではDogのプロトタイプにwalkを持たせてみます。

var Animal = function Animal() { }
var Dog = function Dog() { }

Animal.prototype = {
  walk: function () { console.log('トコトコ in Animal') },
}

Dog.prototype = {
  __proto__: 'Animal',
  walk: function () { console.log('トコトコ in Dog') }
}

var dog = new Dog();

dog.walk();// トコトコ in Dog

Dogで定義したwalkが実行されました。これはAnimalまでPrototype chainで遡らずにDogでwalkメソッドを発見できたためです。

ハマった点①

var Animal = function Animal() { }
var Dog = function Dog() { }

Animal.prototype = {
  walk: function () { console.log('トコトコ in Animal') },
}

Dog.prototype = {
  walk: function () { console.log('トコトコ in Dog') }
}

Dog.prototype = new Animal();// prototypeが上書きされてDog.walkが消える
var dog = new Dog();

dog.walk();// トコトコ in Animal

これは『in Animal』になります。

Dog.prototype = new Animal();でDogオブジェクトにプロトタイプで定義したwalkメソッドが上書きされているからです。

DogのprototypeはAnimalであり、Dogにプロトタイプでwalkを実装したい場合は次のように__proto__プロパティにAnimalが継承元だと記述しましょう。

Dog.prototype = {
  __proto__: 'Animal',
  walk: function () { console.log('トコトコ in Dog') }
}

結局はprototypeというオブジェクトに継承元の情報『__proto__』やメソッド『walk等の追加』をしているだけなので

Dog.prototype.walk = function () { console.log('トコトコ in Dog') }

上記でプロトタイプを追加しているのは、prototypeオブジェクトの要素を一個追加している訳ですね。ここを魔法のように考えてしまっていた私は理解が難しかったです。

ハマった点②

プロトタイプチェーンだとprototyp(継承先)を一つしか定義できないじゃ無いか。多重継承するにはどうすればいいんだろ?

と思ったんですがJavaScriptは単一継承しかサポートしていないのです。多重継承を実現するにはMix-inという考えに基づいてメソッドを定義していかなければならない訳です。

これについてはまた理解が深まったら追記したいと思います。

まとめ

JavaScriptのプロトタイプチェーンについて理解をまとめました。

Objectのprototypeオブジェクトのプロパティに対して継承元の情報を追加することで鎖のようにオブジェクト間が連結してくれることが分かりました。

prototypeオブジェクトは継承元を指し示す『__proto__』や継承元のコンストラクターである『construct』などのプロパティを持ったオブジェクトであれば理解は簡単でした。