プロトタイプと継承

JavaScriptでは、親となるオブジェクトを継承して、オブジェクトが作成されます。

本ページでは、プロトタイプと継承について説明します。

プロトタイプとは

JavaScriptは、プロトタイプベースの言語と言われ、すべてのオブジェクトが他のオブジェクトをプロトタイプ(原型)として作成されます。

プロトタイプの説明

わかりやすい例は、Object.createを用いてオブジェクトを作成した時です。

【Object.createでオブジェクトを作る例】
const japan = {
  country: "Japan",
}

const yamada = Object.create(japan);
yamada.address = "Tokyo";
yamada.birthday = 2001;

最初のconst japan(赤字)部分は、japanオブジェクトを作成しています。次のconst yamada(緑字)部分で、japanをプロトタイプとして、yamadaオブジェクトを作成しています。yamada.addressやyamada.birthdayは、プロパティの追加です。

では、japanオブジェクトは何をプロトタイプにしているかと言えば、デフォルトで存在するObject.prototypeです。また、Object.prototypeのプロトタイプはnullで、nullにはプロトタイプは存在しません。

プロトタイプをたどった先

継承

オブジェクトは、プロトタイプを参照するようになっています。これを継承と呼びます。

継承の説明

yamadaオブジェクト自体にcountryプロパティは存在しなくても、継承によりyamada.countryの値はJapanとなります。

suzukiオブジェクトも、japanをプロトタイプにして作成したとします。

【同じプロトタイプを使ってオブジェクトを作成】
const suzuki = Object.create(japan);
suzuki.address = "Kyoto";
suzuki.birthday = 1999;

addressとbirthdayプロパティの値は各オブジェクトで異なりますが、yamada.countryとsuzuki.countryは継承によって、同じJapanになります。このように、プロトタイプで作成したプロパティは、各オブジェクトで共通して利用できます。

また、japan.country = "日本";を実行すると、yamada.countryもsuzuki.countryも値が日本に変わります。つまり、プロトタイプでの変更は、継承するすべてのオブジェクトに影響します。

yamada.country = "日本";を実行すると、yamada自体にcountryプロパティが作成され、yamada.countryだけ日本に変わります。これは、オーバーライドと呼ばれます。

オーバーライドの説明

上記の場合、suzuki.countryはJapanのままです。つまり、そのオブジェクトにプロパティがない場合だけ、プロトタイプのプロパティが継承されます。

メソッドの継承

プロパティだけでなく、メソッドも継承できます。

【同じプロトタイプを使ってオブジェクトを作成】
const japan = {
  display: function() {
    alert("住所は" + this.address + "で生年月日は" + this.birthday + "年");
  }
}
const yamada = Object.create(japan);
yamada.address = "Tokyo";
yamada.birthday = 2001;
yamada.display();

上記は、最後のyamada.display()を実行した時に、「住所はTokyoで生年月日は2001年」と表示されます。これは、japanオブジェクトからyamadaオブジェクトに継承された、displayメソッドを使って表示されます。

オブジェクトの作り方によって変わるプロトタイプ

オブジェクトの作り方によって、プロトタイプは変わります。次からは、それぞれの作り方で、何がプロトタイプになるのかを説明します。

リテラル表記法

以下は、リテラル表記法でオブジェクトを作成しています。

【リテラル表記法でオブジェクトを作る例】
const yamada = {
  address: "Tokyo",
  birthday: 2001
};

yamadaオブジェクトのプロトタイプは、デフォルトで存在するObject.prototypeです。

new Object()

以下は、new Object()でオブジェクトを作成しています。

【new Object()でオブジェクトを作る例】
const yamada = new Object();
yamada.address = "Tokyo";
yamada.birthday = 2001;

yamadaオブジェクトのプロトタイプは、デフォルトで存在するObject.prototypeです。

コンストラクタ関数

以下は、コンストラクタ関数でオブジェクトを作成しています。

【コンストラクタ関数でオブジェクトを作る例】
function Peaple(address,birthday) {
  this.address = address;
  this.birthday = birthday;
}

const yamada = new Peaple("Tokyo",2001);

yamadaオブジェクトのプロトタイプは、Peaple.prototypeです。関数Peapleのプロパティprototypeが、プロトタイプということです。

コンストラクタ関数でのプロトタイプ

Peaple.prototypeのプロトタイプは、デフォルトで存在するObject.prototypeです。

クラス宣言

以下は、クラス宣言でオブジェクトを作成しています。

【クラス宣言でオブジェクトを作る例】
class Peaple {
  constructor(address, birthday) {
    this.address = address;
    this.birthday = birthday;
  }
}

const yamada = new Peaple("Tokyo",2001);

yamadaオブジェクトのプロトタイプは、Peaple.prototypeです。コンストラクタ関数と同じで、Peaple.prototypeのプロトタイプはObject.prototypeです。

プロトタイプチェーン

プロトタイプから作られたオブジェクトを、さらにプロトタイプにしてオブジェクトを作成できます。このような連鎖をプロトタイプチェーンと言います。

プロトタイプチェーンの説明

以下は、プロトタイプチェーンの例です。

【Object.createを使ったプロトタイプチェーンの例】
const japan = {
    country: "Japan"
};

const job = Object.create(japan);

const yamada = Object.create(job);
yamada.address = "Tokyo";
yamada.birthday = 2001;

const japan(赤字)部分は、japanオブジェクトを作成しています。const job(緑字)部分で、japanをプロトタイプとしてjobオブジェクトを作成しています。const yamada(茶色)部分は、jobをプロトタイプとしてyamadaオブジェクトを作成しています。

yamadaオブジェクトのプロトタイプ(job)の、さらににプロトタイプであるjapanに、countryプロパティがあるため、プロトタイプチェーンによりyamada.countryの値はJapanになります。

プロトタイプチェーンをたどっていくと、nullに到達します。

プロトタイプチェーンによる継承のメリット

プロトタイプチェーンによる継承のメリットは、以下のとおりです。

  • プロパティやメソッドを共通化できるため、各オブジェクトごとに記述しなくて済む。
  • 共通化されたプロパティやメソッドの書き換えは、1回で済む(各オブジェクトで書き換えなくて済む)。
  • 共通化することで、メモリ消費を抑えられる。

1つめと2つめのメリットは、これまでの説明でご理解いただけると思います。プロトタイプで記述したり、値を書き換えたりすれば、それをプロトタイプにしているオブジェクトに継承されるためです。

3つめのメリットですが、各オブジェクトでプロパティを作ると、その分メモリを消費します。しかし、プロトタイプで作って共通化されると、プロトタイプのプロパティやメソッドのメモリだけ消費します。多数のオブジェクトを作るとメモリ消費量も多くなりますが、プロトタイプで作っておけばメモリ消費が少なくて済むということです。

Object.getPrototypeOf

プロトタイプにしているオブジェクトは、Object.getPrototypeOfで確認できます。

【Object.getPrototypeOfの利用例】
const japan = {
};
const yamada = Object.create(japan);
alert(Object.getPrototypeOf(yamada) === japan);

上記は、trueと表示されます。もし、japanがプロトタイプでなければ、falseと表示されます。

[[prototype]]とprototypeと__proto__の違い

オブジェクトのプロトタイプは、内部プロパティの[[prototype]]として定義されています。この[[prototype]]は、直接参照したり書き換えたりはできませんが、すでに説明したObject.getPrototypeOfメソッドなどを使って操作ができます。

この[[prototype]]と__proto__プロパティは、同じものを指します。__protp__は非推奨ですが、多くのブラウザでサポートされていて、直接参照したり書き換えたりできます。例えば、yamada.__proto__ = jobを実行すると、yamadaオブジェクトのプロトタイプがjobになります。

[[prototype]]と__proto__プロパティ

[[prototype]]と、prototypeプロパティは異なります。例えば、コンストラクタ関数の例で示したPeaple.prototypeであれば、Peaple関数のprototypeプロパティということになります。このprototypeプロパティ自体もオブジェクトで、それをプロトタイプにして作成(インスタンス化)したのが、yamadaオブジェクトになります。

[[prototype]]と、prototypeプロパティの違い