前回の記事では、主にベースとなる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
他のオブジェクトのプロトタイプ
Array
やFunction
、Date
など、他のオブジェクトもまたプロトタイプにメソッドを保持しています。
例えば、Array
もObject
と同じように、Array.prototype
を持っています。
Array
のインスタンスは、次のようにArray.prototype
を継承します。
let arr = [];
console.log(arr.__proto__); // []
console.log(arr.__proto__ == Array.prototype); // true
さらに、Array.prototype
はObject.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
のインスタンスは、以下の流れでプロトタイプの継承が行われていると言えます。
Array
のインタンスArray.prototype
Object.prototype
- 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
このように、すべてのオブジェクトは、プロトタイプの先頭にObject.prototype
を持っている、すなわちObject
を継承しているということです。
プリミティブな値
オブジェクトがプロトタイプを継承することは分かりましたが、複雑なことにプリミティブの値でも似たような現象が起こります。
String
やNumber
はオブジェクトではありません。
しかし、これらのプリミティブの値に対してプロパティへアクセスする時、それに対応する一時的なラッパーオブジェクトが作られます。
例えば、文字列がString
のインスタンスメソッドを呼び出すような時です。
let str = 'Hello!';
// プリミティブの値でもメソッドの呼び出しができる
str.toUpperCase();
str
にアクセスする際にラッパーオブジェクトに変換されるため、このような現象が起こります。
詳しくはラッパーオブジェクトの記事で解説していますが、それらのオブジェクトのメソッドも、String.prototype
やNumber.prototype
のように利用可能なプロトタイプに存在します。
まとめ
今回は、オブジェクトとプロトタイプ継承について解説しました。
// ポイント
* すべてのオブジェクトはプロトタイプにメソッドを保持する
* 配列や関数は、Array.prototypeやFunction.prototypeを継承している
* プロトタイプの先頭には、Object.prototypeが存在する
* プリミティブな値でもラッパーオブジェクトによってプロトタイプにメソッドを保持している
オブジェクトがどのようにObject.prototype
を継承し、メソッドを呼び出せているのか分かりました。少しずつ理解を深めていきましょう。
合わせて読みたいプロトタイプシリーズ
第1回:プロトタイプの仕組み
第2回:オブジェクトとプロトタイプ継承(当記事)
第3回:プロトタイプとメソッド
コメント