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

  • 864
    Like
  • 5
    Comment

JavaScriptのリファレンスといえばMDNですよね。
JavaScriptで調べものをする際に、真っ先に見る方も多いでしょう。

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

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


さらっと書かれているprototype

image


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


本記事の目的

  • プロトタイプチェーンを理解する
  • MDNをより読めるようにする
  • JavaScriptのObjectやArrayなどの基本型の構造を理解する

確認環境や記述について

  • Chorme 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のプロトタイプチェーン