プロトタイプチェーンとは
オブジェクトが継承元プロトタイプを参照し、そのプロトタイプがまたその継承元プロトタイプを参照し.....のような連鎖及び、その一連のオブジェクト群のことをプロタイプチェーンといいます。
プロタイプチェーンの仕組み
- 指定したオブジェクトでプロパティの存在を調べる
- なかった場合
__proto__
が参照する先で存在を調べる - それでもなかった場合
__proto__
が参照する・・・(ループ) - 最終的にnullになるまで行う。nullなら
undefined
を返す
プロトタイプチェーンを理解するためには、オブジェクトとオブジェクトプロトタイプを完全に別ものとして考えることが大事です。
配列のプロトタイプチェーン
図で理解するJavaScriptのプロトタイプチェーン - Qiitaで説明されていた配列のプロトタイプチェーンについて補足・解説します。
① var myArray = [];
② myArray.push("hoge"); // これはプロトタイプチェーン先のメソッドから呼べる
③ myArray.isArray(); // undefined()を実行しているようなもの
④ Array.isArray(myArray); // true isArrayの使い方はこう
① 配列の定義
① var myArray = [];
これはmyArray
配列を定義、つまりmyArrayインスタンス
を生成している以下の記述と同義です。
var myArray = new Array();
それと同時に、コンストラクタ関数であるArrayオブジェクト
のprototype
が、myArray.__proto__
に代入されます。
__proto__は全てのオブジェクトが持つ、内部プロパティ。そのオブジェクト自体のprototypeへの参照を持つ。
myArray.__proto__ = Array.prototype; // Arrayオブジェクトのプロトタイプを代入
② pushメソッドはどこにある?
② myArray.push("hoge");
myArrayオブジェクト自体にはpushメソッド
は存在しません。
オブジェクトにプロパティが存在しない場合、プロトタイプチェーンの仕組みにより、そのオブジェクトの__proto__
が参照する先でプロパティの存在有無を確認します。
①よりmyArray.__proto__ === Array.prototype;
なので、myArray
はArray.prototype
が持つpushメソッド
を参照することができます。
myArray.push("hoge"); // myArrayオブジェクトにはpushメソッドは存在しない。
↓ // myArray.__proto__を参照する。
myArray.__proto__ === Array.prototype // true
↓ // Array.prototypeにpushメソッドがないか確認する。
Array.prototype.push // Array.prototypeにはpushメソッドが存在するので、myArrayはpushメソッドを使える。
③ myArray.isArray()はなぜエラーになる?
③ myArray.isArray(); // undefined()を実行しているようなもの
isArray()
とは、コンストラクタ関数Array
が持つメソッドの一つです。
myArray
はそのプロトタイプを含めてisArray()
を所持していないので、③式はundefined
になります。
myArray.isArray(); // myArrayオブジェクトにはisArray()は存在しない。
↓ // myArray.__protoを参照する。
myArray.__proto__ === Array.prototype // Array.prototypeにもisArray()は存在しない。
↓ // Array.prototype.__protoを参照する。
Array.prototype.__proto__ === Object.prototype // Object.prototypeにもisArray()は存在しない。
↓ // Object.prototype.__proto__を参照する。
Object.prototype.__proto__ === null // Object.prototype.__proto__はnullなので③式はundefinedとなる。
ほぼ全てのオブジェクトはObject.prototype
を始祖に持ち1、Object.prototype
がプロトタイプチェーンの終端となります。
④ isArray()を正しく使うには?
インスタンスはconstructorプロパティ
を持っています。
そしてそのconstructorプロパティは、インスタンス化の元となったオブジェクト(継承元)のコンストラクタ関数を参照します。
つまり、myArray.constructor
はその元となったオブジェクトであるArray
を参照しています。
myArray.constructor === Array; // true
ArrayオブジェクトはisArrayメソッド
を所持しているので、isArrayの正しい使い方は以下になります。
④ Array.isArray(myArray); // true
myArray.constructor.isArray(myArray); // true
Array.prototype.constructor.isArray(myArray); // true
以上が配列を定義した際のプロトタイプチェーン解説です。
Objectオブジェクトのプロトタイプチェーン
すべてのオブジェクトは__proto__プロパティ
を持つので、それはObjectオブジェクト
も同様です。
プロトタイプチェーンの末端であるObject.prototype
を参照するのかと思いきや、以下記述はfalseになります。
Object.__proto__ === Object.prototype // false
それではObject.__proto__
は何を参照しているのでしょうか?
答えはFunction.prototype
です。
Object.__proto__ === Function.prototype // true
Function.prototype
は、Object.prototype
を参照することでプロトタイプチェーンの末端にたどり着きます。
Object.__proto__ === Function.prototype // true
↓
Function.prototype.__proto__ === Object.prototype; // true
↓
Object.prototype.__proto__ === null // true
Functionオブジェクトのプロトタイプチェーン
Functionオブジェクト
の__proto__
もFunction.prototype
を参照し、Objectオブジェクトと同様のチェーンを辿ります。
Function.__proto__ === Function.prototype // true
↓
Function.prototype.__proto__ === Object.prototype; // true
↓
Object.prototype.__proto__ === null // true
toStringメソッドのややこしい話
Objectオブジェクトの持つメソッドのひとつに、toString()
があります。
Object.prototype.toString() - JavaScript | MDN
以下の例は同じtoStringメソッド
を参照しているように見えますが、結果が異なります。
Object.toString === Object.__proto__.toString // true
Object.toString === Object.prototype.toString // false
理由を調べるため、Object.toString
のプロトタイプチェーンを辿っていくと、
その参照先はFunction.prototype
を指していることが分かりました。
Object.toString // ObjectオブジェクトにはtoStringメソッドは存在しない
↓ // Object.__proto__を参照する
Object.__proto__ === Function.prototype // true
↓ // Function.prototypeにtoStringメソッドがないか確認する。
Function.prototype.toString // Function.prototypeにはtoStringメソッドが存在するので、ObjectはtoStringメソッドを使える。
↓ // Function.prototype.__proto__も見てみる。
Object.prototype.toString // 親(prototype)と子で同名のメソッドを持つ場合、親メソッドは子メソッドでオーバーライド(上書き)される。
Object.prototype
が持つtoStringメソッド
と、Function.prototype
が持つtoStringメソッド
ではメソッドの内容が異なります。
Objectオブジェクト
はその両者とも継承していますが、メソッドのオーバーライドにより、親(Object.prototype
)のメソッドが子(Function.prototype
)のメソッドにより上書かれています。
よって持っているtoStringメソッド
の起源が異なることが、最初の例の真相です。
Object.toString === Object.__proto__.toString // true
↓ // 書き換えると
Function.prototype.toString === Function.prototype.toString // true
Object.toString === Object.prototype.toString // false
↓ // 書き換えると
Function.prototype.toString === Object.prototype.toString // false
Object.prototype.toString()とFunction.prototype.toString()は別のメソッドなんですね。
参考
図で理解するJavaScriptのプロトタイプチェーン - Qiita
なるほど。。__proto__とprototypeの違い | 武骨日記
継承とプロトタイプチェーン - JavaScript | MDN
[Object - JavaScript | MDN]
(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object)
Object.prototype - JavaScript | MDN
Object.prototype.__proto__ - JavaScript | MDN
Object.prototype.toString() - JavaScript | MDN
Object.create() - JavaScript | MDN
Function - JavaScript | MDN
Function.prototype.toString() - JavaScript | MDN
-
例えば
Object.create(null)
で作られたオブジェクトはプロトタイプを持たないので、Object.prototype
から継承を受けていない。 ↩