前回の記事では、主にベースとなるObjectを基準にプロトタイプの仕組みについて解説しました。
しかし、他のオブジェクトがどのようにプロトタイプを継承しているのか、まだピンときていないかもしれません。

そこで今回は、オブジェクトとプロトタイプ継承について解説していきます。

Objectのプロトタイプ

まずは単純なObjectのプロトタイプから見ていきましょう。
ここでは、__proto__プロパティを使ってプロトタイプを確認していきます。

オブジェクトリテラルでobjが作成された時、Object.prototypeが継承されます。

let obj = {};

console.log(obj.__proto__); // {}

// Object.prototypeを継承している
console.log(obj.__proto__ == Object.prototype); // true

その後、プロトタイプメソッドが呼ばれると、Object.prototypeからメソッドが取り出されます。
例えば、obj.toString()の場合、このようにして確認できます。

let obj = {};

console.log(obj.toString == obj.__proto__.toString); // true
console.log(obj.toString == Object.prototype.toString); // true

また、obj.__proto__.__proto__は、追加のプロトタイプがありません。

console.log(obj.__proto__.__proto__); // null

他のオブジェクトのプロトタイプ

ArrayFunctionDateなど、他のオブジェクトもまたプロトタイプにメソッドを保持しています。

例えば、ArrayObjectと同じように、Array.prototypeを持っています。
Arrayのインスタンスは、次のようにArray.prototypeを継承します。

let arr = [];

console.log(arr.__proto__); // []
console.log(arr.__proto__ == Array.prototype); // true

さらに、Array.prototypeObject.prototypeを継承しているため、ArrayのインスタンスはObject.prototypeも継承していることになります。

let arr = [];

// Array.prototypeを継承している
console.log(arr.__proto__); // []

// Object.prototypeを継承している
console.log(arr.__proto__.__proto__); // {}

ArrayのインスタンスがObject.prototypeを継承しているということから、Object.prototypeに定義されているメソッドが利用できるということです。

以下は、hasOwnPropertyメソッドを呼び出した例です。

let arr = [];

console.log(arr.hasOwnProperty); // function hasOwnProperty() {}

// Array.prototypeを継承している
console.log(arr.hasOwnProperty == arr.__proto__.hasOwnProperty); // true

// Object.prototypeを継承している
console.log(arr.hasOwnProperty == Object.prototype.hasOwnProperty); // true

そして、プロトタイプチェーンをたどると最終的にnullになります。

console.log(arr.__proto__.__proto__.__proto__); // null

このことから、Arrayのインスタンスは、以下の流れでプロトタイプの継承が行われていると言えます。

  1. Arrayのインタンス
  2. Array.prototype
  3. Object.prototype
  4. null

これは、Functionも同じように動作します。

function fn() {}

// Function.prototypeを継承している
console.log(fn.__proto__); // function() {}

// Object.prototypeを継承している
console.log(fn.__proto__.__proto__); // {}

console.log(fn.__proto__.__proto__.__proto__); // null

Objects-prototype

このように、すべてのオブジェクトは、プロトタイプの先頭にObject.prototypeを持っている、すなわちObjectを継承しているということです。

プリミティブな値

オブジェクトがプロトタイプを継承することは分かりましたが、複雑なことにプリミティブの値でも似たような現象が起こります。

StringNumberはオブジェクトではありません。
しかし、これらのプリミティブの値に対してプロパティへアクセスする時、それに対応する一時的なラッパーオブジェクトが作られます。

例えば、文字列がStringのインスタンスメソッドを呼び出すような時です。

let str = 'Hello!';

// プリミティブの値でもメソッドの呼び出しができる
str.toUpperCase();

strにアクセスする際にラッパーオブジェクトに変換されるため、このような現象が起こります。
詳しくはラッパーオブジェクトの記事で解説していますが、それらのオブジェクトのメソッドも、String.prototypeNumber.prototypeのように利用可能なプロトタイプに存在します。

まとめ

今回は、オブジェクトとプロトタイプ継承について解説しました。

// ポイント
* すべてのオブジェクトはプロトタイプにメソッドを保持する
* 配列や関数は、Array.prototypeやFunction.prototypeを継承している
* プロトタイプの先頭には、Object.prototypeが存在する
* プリミティブな値でもラッパーオブジェクトによってプロトタイプにメソッドを保持している

オブジェクトがどのようにObject.prototypeを継承し、メソッドを呼び出せているのか分かりました。少しずつ理解を深めていきましょう。

合わせて読みたいプロトタイプシリーズ

第1回:プロトタイプの仕組み
第2回:オブジェクトとプロトタイプ継承(当記事)
第3回:プロトタイプとメソッド