prototypeの正体
prototypeってものを本で読んだだけでわかったつもりになっていたけど、いざ使ってみたら勘違いばかりでダメダメだったので覚書。やっぱ何か覚えるときはコードの写経だけでもしといたほうがいい気がする。
動かないコード
こんなコードを動かそうとして失敗した。
var proto = { prop:"aaaaaa", alert:function(){ alert(this.prop); } }; var base = function(){ this.prototype=proto; }; var test = new base(); test.alert();
で、こんなエラーが出る。
test.alert is not a function
明らかに
this.prototype=proto;
が怪しいので、こいつをコメントアウトして、さらにbaseコンストラクタの前で
base.prototype=proto;
をしてやる
var proto = { prop:"aaaaaa", alert:function(){ alert(this.prop); } }; var base = function(){ //this.prototype=proto; }; base.prototype=proto; var test = new base(); test.alert();
こうすると test.alert(); が成功する。
コンストラクタの中でprototypeの指定までできたらスッキリしていいなぁと思ってエラーになってるthis.prototype云々という行をいれたけど、案の定ダメだった。このコンストラクタの宣言の前でprototypeとなるオブジェクト(proto)を定義してあるからよさそうな気もするんだけどなあ。
prototypeと__proto__
var a = function(){this.x = "prop x"};
console.dir(a);
このコードを実行してみる。console.dirで以下のような値が出力された。
「prototype」と「__proto__」というなんか怪しげなプロパティがでてくる。こいつらは何なのか?
var a = function(){this.x = "prop x"};
var b = function(){this.y = "prop y"};
b.prototype = new a();
console.dir(b);
var objB = new b();
console.dir(objB);
今度はこんな感じで2箇所にconsole.dirを設置してみる。
1つ目は関数オブジェクト b に対するもの…(1)
2つ目はbをコンストラクタとして生成されたobjBというオブジェクト…(2)
(1)では「prototype」にaが入り、__proto__は空の関数オブジェクトが格納されている。(2)では「prototype」というプロパティはなく、__proto__にbの「prototype」として追加した a が入っている。どういうこと?
prototypeの役割
そもそもprototypeがしてくれるのは、実行時のプロパティまたはメソッドの検索。これは多くのjavascript入門本なんかにも書かれている。が、ここが紛らわしい(とこだと個人的に思う)。
javascriptは実行時に以下の順序でプロパティを探す。
1. そのプロパティが属するオブジェクト
2. そのオブジェクトの__proto__プロパティとして定義されている__proto__オブジェクト
3. __proto__チェーン上の__proto__オブジェクトを再帰的に検索
4. 最上位のObjectの__proto__オブジェクトにもない場合は undefined を返却
つまりプロトタイプチェーンの実体は__proto__にセットされているオブジェクトを再帰的に検索しているもの。上記の(2)のケースでは objB の__proto__には a が入っていたので、objBのプロトタイプチェーン上には a がいるということになる。なのでalert(objB.x)などとやったら "prop x" という文字列が出力される。
__proto__の役割はわかったけど、じゃあ「prototype」プロパティとはなんなのか?それと__proto__との関係はどうなのか?
コンストラクタによるオブジェクト生成の瞬間
newと関数を組み合わせた形式 new SomeFunc(mayHaveArguments); でオブジェクトが生まれるのですが、そのとき、次の手順に従います。
- 処理系が、プレーンなオブジェクトをヒープにアロケートする。
- コンストラクタ(として使われる)関数の呼び出し環境(Callオブジェクト、スタックフレームに相当)のthisとして、今アロケートしたオブジェクトをセットする。
- コンストラクタ関数のコードが実行される。
- thisに入っていたオブジェクトが返される。
大筋はこういうことですね。おっと、大事なことが抜けている。そう、__proto__プロパティのセットです。
●生成したオブジェクトの__proto__プロパティとして、そのコンストラクタ関数のprototypeオブジェクトをセットする。
このことから、オブジェクト生成直後の__proto__オブジェクトは、コンストラクのprototypeオブジェクトと一致することになります。
http://d.hatena.ne.jp/m-hiyama/20050909/1126235062
まぁそういうわけなんです。
「prototype」プロパティはすべての関数オブジェクトがもつプロパティ。なのでそれを基にして上記のような手順でprototypeが__proto__に設定される。
ちなみにこの__proto__というのはjavascriptの独自実装で、ES3には含まれていない。ES5で__proto__と同価の Object.getPrototypeOf() というメソッドが追加された、って最近話題の「パーフェクトjavascript」を立ち読みしたら書いてありました。http://www.ibm.com/developerworks/jp/web/library/wa-ecma262/