Edited at

図で理解するJavaScriptのプロトタイプチェーン

More than 1 year has passed since last update.

JavaScriptのリファレンスといえばMDNですよね。

JavaScriptで調べものをする際に、真っ先に見る方も多いでしょう。

そんなMDNですが読めていますか?

例えばArrayのページを見てみましょう。



さらっと書かれているprototype

image



JavaScriptを理解するにはプロトタイプチェーンを知る必要がある



本記事の目的


  • プロトタイプチェーンを理解する

  • MDNをより読めるようにする

  • JavaScriptのObjectやArrayなどの基本型の構造を理解する



確認環境や記述について


  • Chrome 52のコンソールで動作確認

  • ES5で記述

  • 説明のしやすさを重視、プロトタイプをあらわすのに__proto__を使って記述



まずはオブジェクトについてみていく



シンプルなオブジェクトを作る


リテラルでオブジェクトを作る

var obj = {name: "taro"};



new演算子でオブジェクトを作る

var obj = new Object();

obj.name = "taro";

上記2つの同じオブジェクトを作っています。

{}new Object()の糖衣構文



console.logで中身を出力してみる

var obj = {name: "taro"};

console.log(obj);

image

Object {name: "taro"}というのが出てきました。



Objectを展開

image

__proto__という未知のプロパティが出てきます。

これはオブジェクトを作ると裏で作られるプロパティです。



__proto__のObjectを展開

プロパティがたくさん設定されているオブジェクトの様です。

image



ここまでの図解

image



謎のオブジェクトの正体とは



MDNで検索

出てきたFunction名で検索をかけるとObjectプロトタイプオブジェクトに含まれるメソッドと一致します。

image



謎のオブジェクトの正体はObject.prototype


確認

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


上記コードでtrueになるのが確認できます。



Object自体がオブジェクト

Object.prototypeと言う書き方はobj.nameのような書き方と全く同じです。

Objectという変数名でオブジェクトが定義されており、Objectのプロパティにprototypeが設定されています。


あくまでイメージです

var Object = {prototype: {オブジェクトのプロトタイプ}};




ここまでの図解

image



そもそもObjectとは



Objectという変数はwindowに定義されている

このObjectという変数はブラウザのグローバルオブジェクトであるwindowに定義されています。


確認

obj.__proto__ === window.Object.prototype; // true




おまけ:objという変数もwindowに定義されている

自分で作ったこのobjという変数もブラウザのグローバルオブジェクトであるwindowに定義しています。


確認

window.obj.__proto__ === window.Object.prototype; // true




ここまでの図解

image



いよいよプロトタイプチェーンに触れていく


MDNのリファレンスを見るとObject.prototypeにはtoString()というメソッドがあることがわかる。

試しに呼んでみる。

console.log(obj.name); // taro -> わかる

console.log(
obj.__proto__.toString
); // function toString() { [native code] } -> 実装はネイティブコードらしい



これはどうなる?

console.log(

obj.toString
); // function toString() { [native code] } -> 呼べてしまった

console.log(
obj.hogehoge
); // undefined -> 未定義?



プロトタイプチェーンの仕組み


  1. 指定したオブジェクトにプロパティが存在を調べる

  2. なかった場合__proto__が参照する先で存在を調べる

  3. それでもなかった場合__proto__が参照する・・・(ループ)

  4. 最終的にnullになるまで行う。nullならundefinedを返す



Objectのプロトタイプの__proto__はnull

obj.__proto__.__proto__ === null; // true

Object.prototype.__proto__; // null

つまり__proto__を辿ってObject.prototypeで見つからなかったらundefinedを返すということ。

obj.__proto__Object.prototypeは同じものです(重要なので何度でも書く)



ここまでの図解

image



プロトタイプチェーンの流れを見る

先程記述したそれぞれのパターンごとに動きを見てみます。


  • obj.name (インスタンスに存在するパターン)

  • obj.toString (プロトタイプチェーン先に存在するパターン)

  • obj.hogehoge (最後まで見つからないパターン)



obj.nameを探す際の動き



1.objを探す

プロトタイプチェーン1_1.png



2.obj.nameを探す

image



obj.toStringを探す際の動き



1.objを探す

プロトタイプチェーン1_1.png



2.obj.toStringを探す

プロトタイプチェーン2_2.png



3.obj.__proto__を探す

プロトタイプチェーン2_3.png



4.obj.__proto__.toStringを探す

image



obj.hogehogeを探す際の動き



1.objを探す

プロトタイプチェーン1_1.png



2.obj.hogehogeを探す

プロトタイプチェーン3_2.png



3.obj.__proto__を探す

プロトタイプチェーン2_3.png



4.obj.__proto__.hogehogeを探す

プロトタイプチェーン3_4.png



5.obj.__proto__.__proto__を探す

image



Functionオブジェクトについて

さらにプロトタイプチェーンを深く知るにはFunctionオブジェクトについて知る必要があります。

※Objectでだいぶプロトタイプチェーンに強くなっているはずです。説明もささっといきます。



シンプルなFunctionオブジェクトを作る


リテラルでFunctionオブジェクトを作る

var func = function(){console.log("hello");};



new演算子でFunctionオブジェクトを作る

var func = new Function('console.log("hello");');


上記2つのほぼ同じオブジェクトを作っています。(後者はfunc.nameが"anonymous"になったりする)

function(){}new Function()の糖衣構文



Functionの__proto__

Objectのインスタンスの__proto__はObject.prototypeでしたね。

(new Object()).__proto__ === Object.prototype; // true

この形はObject以外でも同じです。

(new Function()).__proto__ === Function.prototype; // true

(new Array()).__proto__ === Array.prototype; // true



つまり、FunctionもObjectと同じようなプロトタイプが構成されている



ここまでの図解

image



Functionのプロトタイプにも__proto__がある

プロトタイプチェーンは__proto__を辿るんでしたね。

みてみます。


__proto__を辿る

console.log(func.__proto__.__proto__);


image



これObjectのときに出てきたやつだ!!



FunctionもObjectのプロトタイプチェーンが組まれている


確認

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


Function.prototypeにtoStringが定義されているため(いわゆるオーバーライド)

toStringはFunction.prototype.toStringになります。


確認

func.toString(); // function (){console.log("hello");}

func.toString === Function.prototype.toString; // true;



ここまでの図解

image



コンストラクタ関数

コンストラクタ関数はnew XXX()に入るXXX関数のこと。

つまりObjectFunctionなどはコンストラクタ関数として使用できるFunctionオブジェクト。



constructorプロパティ

コンストラクタ関数を参照するためのプロパティ。

プロトタイプやインスタンスが持っている。


今まで出てきた例のconstructor

var obj = {};

Object.prototype.constructor === Object; // true
obj.constructor === Object; // true

var func = function(){};
Function.prototype.constructor === Function; // true
func.constructor === Function; // true




ここまでの図解

image



最初に見たArrayのMDNにアクセスして見てみよう

image



ある程度は読めるようになったはず

なってなかったら・・・筆者の説明スキル不足、申し訳ない :cry:

本記事の内容が理解できていれば

MDNと以下のコードで次ページのようなイメージができるはずです。


配列を生成

var myArray = [];

myArray.push("hoge"); // これはプロトタイプチェーン先のメソッドから呼べる
myArray.isArray; // 何が返るかわかりますね?
// myArray.isArray(); // undefined()を実行しているようなもの
Array.isArray(myArray); // true isArrayの使い方はこう



image



あとがき

プロトタイプチェーンを理解できた人が一人でも多くいれば幸いです。

本記事の目的はプロトタイプチェーンを理解することです。

プロトタイプチェーンを使ってごりごり実装しようということではありません。

原則としてはclass構文使いましょう。(classも内部的にはプロトタイプチェーン)



参考資料

書籍

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方

JavaScript Ninjaの極意

記事

[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成

や...やっと理解できた!JavaScriptのプロトタイプチェーン