LoginSignup
12
7

More than 3 years have passed since last update.

JavaScriptのプロトタイプチェーンを深堀りする

Last updated at Posted at 2020-02-24

プロトタイプチェーンとは

オブジェクトが継承元プロトタイプを参照し、そのプロトタイプがまたその継承元プロトタイプを参照し.....のような連鎖及び、その一連のオブジェクト群のことをプロタイプチェーンといいます。

プロタイプチェーンの仕組み
1. 指定したオブジェクトでプロパティの存在を調べる
2. なかった場合__proto__が参照する先で存在を調べる
3. それでもなかった場合__proto__が参照する・・・(ループ)
4. 最終的にnullになるまで行う。nullならundefinedを返す

図で理解するJavaScriptのプロトタイプチェーン - Qiitaより引用

プロトタイプチェーンを理解するためには、オブジェクトオブジェクトプロトタイプを完全に別ものとして考えることが大事です。

配列のプロトタイプチェーン

図で理解するJavaScriptのプロトタイプチェーン - Qiitaで説明されていた配列のプロトタイプチェーンについて補足・解説します。

配列を生成(記事より引用)
 var myArray = [];
 myArray.push("hoge"); // これはプロトタイプチェーン先のメソッドから呼べる
 myArray.isArray(); // undefined()を実行しているようなもの
 Array.isArray(myArray); // true isArrayの使い方はこう

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3332313333382f35323234656134352d323930302d323234372d323739632d3833373939306661376161652e706e67.png

① 配列の定義

空の配列を定義
 var myArray = [];

これはmyArray配列を定義、つまりmyArrayインスタンスを生成している以下の記述と同義です。

Arrayオブジェクトのインスタンス化
var myArray = new Array();

それと同時に、コンストラクタ関数であるArrayオブジェクトprototypeが、myArray.__proto__に代入されます。

__proto__は全てのオブジェクトが持つ、内部プロパティ。そのオブジェクト自体のprototypeへの参照を持つ。

なるほど。。__proto__とprototypeの違い | 武骨日記より引用

同時に起こる
myArray.__proto__ = Array.prototype;   // Arrayオブジェクトのプロトタイプを代入

スクリーンショット 2020-02-28 20.00.03.png

② pushメソッドはどこにある?

配列に要素を追加するpushメソッド
 myArray.push("hoge");

myArrayオブジェクト自体にはpushメソッドは存在しません。
オブジェクトにプロパティが存在しない場合、プロトタイプチェーンの仕組みにより、そのオブジェクトの__proto__が参照する先でプロパティの存在有無を確認します。

①よりmyArray.__proto__ === Array.prototype;なので、myArrayArray.prototypeが持つpushメソッドを参照することができます。

myArrayがpushメソッドを使える理由
myArray.push("hoge");  // myArrayオブジェクトにはpushメソッドは存在しない。

  // myArray.__proto__を参照する。

myArray.__proto__ === Array.prototype  // true

  // Array.prototypeにpushメソッドがないか確認する。

Array.prototype.push  // Array.prototypeにはpushメソッドが存在するので、myArrayはpushメソッドを使える。

スクリーンショット 2020-02-28 19.55.40.png

③ myArray.isArray()はなぜエラーになる?

なぜundefined?
 myArray.isArray(); // undefined()を実行しているようなもの

isArray()とは、コンストラクタ関数Arrayが持つメソッドの一つです。
myArrayはそのプロトタイプを含めてisArray()を所持していないので、③式はundefinedになります。

myArrayインスタンスはisArrayメソッドを参照できない
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を始祖に持ち1Object.prototypeプロトタイプチェーンの終端となります。
スクリーンショット 2020-02-28 20.04.06.png

④ isArray()を正しく使うには?

インスタンスはconstructorプロパティを持っています。
そしてそのconstructorプロパティは、インスタンス化の元となったオブジェクト(継承元)のコンストラクタ関数を参照します。

つまり、myArray.constructorはその元となったオブジェクトであるArrayを参照しています。

myArrayのconstructorプロパティはArrayオブジェクトを参照する
myArray.constructor === Array;  // true

ArrayオブジェクトはisArrayメソッドを所持しているので、isArrayの正しい使い方は以下になります。

isArray()を正しく使うパターン
 Array.isArray(myArray);  // true

myArray.constructor.isArray(myArray);  // true

Array.prototype.constructor.isArray(myArray);  // true

スクリーンショット 2020-02-28 19.41.10.png

以上が配列を定義した際のプロトタイプチェーン解説です。

Objectオブジェクトのプロトタイプチェーン

すべてのオブジェクトは__proto__プロパティを持つので、それはObjectオブジェクトも同様です。
プロトタイプチェーンの末端であるObject.prototypeを参照するのかと思いきや、以下記述はfalseになります。

Object.__proto__の参照元はObject.prototypeではない
Object.__proto__ === Object.prototype  // false

それではObject.__proto__は何を参照しているのでしょうか?

答えはFunction.prototypeです。

Object.__proto__の参照元はFunction.prototype
Object.__proto__ === Function.prototype  // true

Function.prototypeは、Object.prototypeを参照することでプロトタイプチェーンの末端にたどり着きます。

Objectオブジェクトのプロトタイプチェーン
Object.__proto__ === Function.prototype  // true

Function.prototype.__proto__ === Object.prototype;  // true

Object.prototype.__proto__ === null  // true

スクリーンショット 2020-02-28 20.20.30.png

Functionオブジェクトのプロトタイプチェーン

Functionオブジェクト__proto__Function.prototypeを参照し、Objectオブジェクトと同様のチェーンを辿ります。

Functionオブジェクトのプロトタイプチェーン
Function.__proto__ === Function.prototype  // true

Function.prototype.__proto__ === Object.prototype;  // true

Object.prototype.__proto__ === null  // true

スクリーンショット 2020-02-28 20.25.07.png

toStringメソッドのややこしい話

Objectオブジェクトの持つメソッドのひとつに、toString()があります。
Object.prototype.toString() - JavaScript | MDN

以下の例は同じtoStringメソッドを参照しているように見えますが、結果が異なります。

toStringメソッドの比較
Object.toString === Object.__proto__.toString  // true
Object.toString === Object.prototype.toString  // false


理由を調べるため、Object.toStringのプロトタイプチェーンを辿っていくと、
その参照先はFunction.prototypeを指していることが分かりました。

Object.toStringのプロトタイプチェーン
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)のメソッドにより上書かれています。

スクリーンショット 2020-02-28 22.05.16.png

よって持っているtoStringメソッドの起源が異なることが、最初の例の真相です。

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
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


  1. 例えばObject.create(null)で作られたオブジェクトはプロトタイプを持たないので、Object.prototypeから継承を受けていない。 

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7