JavaScriptのリファレンスといえばMDNですよね。
JavaScriptで調べものをする際に、真っ先に見る方も多いでしょう。
そんなMDNですが読めていますか?
例えばArrayのページを見てみましょう。
さらっと書かれているprototype
JavaScriptを理解するにはプロトタイプチェーンを知る必要がある
本記事の目的
- プロトタイプチェーンを理解する
- MDNをより読めるようにする
- JavaScriptのObjectやArrayなどの基本型の構造を理解する
確認環境や記述について
- Chrome 52のコンソールで動作確認
- ES5で記述
- 説明のしやすさを重視、プロトタイプをあらわすのに
__proto__
を使って記述
まずはオブジェクトについてみていく
シンプルなオブジェクトを作る
var obj = {name: "taro"};
var obj = new Object();
obj.name = "taro";
上記2つの同じオブジェクトを作っています。
※{}
はnew Object()
の糖衣構文
console.logで中身を出力してみる
var obj = {name: "taro"};
console.log(obj);
Object {name: "taro"}
というのが出てきました。
Objectを展開
__proto__
という未知のプロパティが出てきます。
これはオブジェクトを作ると裏で作られるプロパティです。
__proto__
のObjectを展開
プロパティがたくさん設定されているオブジェクトの様です。
ここまでの図解
謎のオブジェクトの正体とは
MDNで検索
出てきたFunction名で検索をかけるとObjectプロトタイプオブジェクトに含まれるメソッドと一致します。
謎のオブジェクトの正体はObject.prototype
obj.__proto__ === Object.prototype; // true
上記コードでtrue
になるのが確認できます。
Object
自体がオブジェクト
Object.prototype
と言う書き方はobj.name
のような書き方と全く同じです。
Object
という変数名でオブジェクトが定義されており、Objectのプロパティにprototypeが設定されています。
var Object = {prototype: {オブジェクトのプロトタイプ}};
ここまでの図解
そもそもObject
とは
Object
という変数はwindow
に定義されている
このObject
という変数はブラウザのグローバルオブジェクトであるwindow
に定義されています。
obj.__proto__ === window.Object.prototype; // true
おまけ:obj
という変数もwindow
に定義されている
自分で作ったこのobj
という変数もブラウザのグローバルオブジェクトであるwindow
に定義しています。
window.obj.__proto__ === window.Object.prototype; // true
ここまでの図解
いよいよプロトタイプチェーンに触れていく
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 -> 未定義?
プロトタイプチェーンの仕組み
- 指定したオブジェクトにプロパティが存在を調べる
- なかった場合
__proto__
が参照する先で存在を調べる - それでもなかった場合
__proto__
が参照する・・・(ループ) - 最終的にnullになるまで行う。nullなら
undefined
を返す
Objectのプロトタイプの__proto__
はnull
obj.__proto__.__proto__ === null; // true
Object.prototype.__proto__; // null
つまり__proto__
を辿ってObject.prototypeで見つからなかったらundefined
を返すということ。
※obj.__proto__
とObject.prototype
は同じものです(重要なので何度でも書く)
ここまでの図解
プロトタイプチェーンの流れを見る
先程記述したそれぞれのパターンごとに動きを見てみます。
- obj.name (インスタンスに存在するパターン)
- obj.toString (プロトタイプチェーン先に存在するパターン)
- obj.hogehoge (最後まで見つからないパターン)
obj.name
を探す際の動き
1.obj
を探す
2.obj.name
を探す
obj.toString
を探す際の動き
1.obj
を探す
2.obj.toString
を探す
3.obj.__proto__
を探す
4.obj.__proto__.toString
を探す
obj.hogehoge
を探す際の動き
1.obj
を探す
2.obj.hogehoge
を探す
3.obj.__proto__
を探す
4.obj.__proto__.hogehoge
を探す
5.obj.__proto__.__proto__
を探す
Functionオブジェクトについて
さらにプロトタイプチェーンを深く知るにはFunctionオブジェクトについて知る必要があります。
※Objectでだいぶプロトタイプチェーンに強くなっているはずです。説明もささっといきます。
シンプルなFunctionオブジェクトを作る
var func = function(){console.log("hello");};
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と同じようなプロトタイプが構成されている
ここまでの図解
Functionのプロトタイプにも__proto__
がある
プロトタイプチェーンは__proto__
を辿るんでしたね。
みてみます。
console.log(func.__proto__.__proto__);
これ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;
ここまでの図解
コンストラクタ関数
コンストラクタ関数はnew XXX()
に入るXXX
関数のこと。
つまりObject
やFunction
などはコンストラクタ関数として使用できるFunctionオブジェクト。
constructorプロパティ
コンストラクタ関数を参照するためのプロパティ。
プロトタイプやインスタンスが持っている。
var obj = {};
Object.prototype.constructor === Object; // true
obj.constructor === Object; // true
var func = function(){};
Function.prototype.constructor === Function; // true
func.constructor === Function; // true
ここまでの図解
最初に見たArrayのMDNにアクセスして見てみよう
ある程度は読めるようになったはず
なってなかったら・・・筆者の説明スキル不足、申し訳ない
本記事の内容が理解できていれば
MDNと以下のコードで次ページのようなイメージができるはずです。
var myArray = [];
myArray.push("hoge"); // これはプロトタイプチェーン先のメソッドから呼べる
myArray.isArray; // 何が返るかわかりますね?
// myArray.isArray(); // undefined()を実行しているようなもの
Array.isArray(myArray); // true isArrayの使い方はこう
あとがき
プロトタイプチェーンを理解できた人が一人でも多くいれば幸いです。
本記事の目的はプロトタイプチェーンを理解することです。
プロトタイプチェーンを使ってごりごり実装しようということではありません。
原則としてはclass構文
使いましょう。(classも内部的にはプロトタイプチェーン)
参考資料
書籍
開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質
Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方
JavaScript Ninjaの極意
記事
[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成
や...やっと理解できた!JavaScriptのプロトタイプチェーン