プロトタイプチェーンとは
オブジェクトが継承元プロトタイプを参照し、そのプロトタイプがまたその継承元プロトタイプを参照し.....のような連鎖及び、その一連のオブジェクト群のことをプロタイプチェーンといいます。
プロタイプチェーンの仕組み
- 指定したオブジェクトでプロパティの存在を調べる
- なかった場合
__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から継承を受けていない。 ↩
