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






















コメント