prototypeとnew

prototypeについて、まだ理解できなかったのでいろいろ調べまわった
だいぶわかってきたぞ。

hasOwnPropertyで調べてみる

JavaScriptの全てのオブジェクトには、hasOwnPropertyというメソッドが定義されている。

var Class = function(){}
Class.prototype = {
	hoge : 'piyo'
};
var obj = new Class();
alert(obj.hasOwnProperty(`hoge`));//(1-1)false
obj.hoge = 'fuga';
alert(obj.hasOwnProperty(`hoge`));//(1-2)true

hasOwnPropertyを使うと、そのオブジェクトにメンバがあるかどうかを返す。
(1-1)の時点では、obj.prototypeにプロパティhogeがあるだけで、objには無く、(1-2)ではobjにhogeがあるのでtrue。
この例だと最終的には、objにもobj.prototypeにもhogeがあるということ。

new演算子の動き

なんかちょっとわかりやすいのみつけたのです。
prototype図示とnewの挙動 - web newbie
からたどって。。
JavaScriptのnew演算子の意味 - Days on the Moon
ここらから引用と自分なりに勉強して書かせていただきました。っと、このあたりを見つけたので今回のエントリ書けたです。感謝。

prototype指向はこうするとわかりやすい! - web newbie

  • 1:prototypeとオブジェクトの関連を図でイメージする。
  • 2:newの挙動を把握する。

new演算子の挙動 - Days on the Moon

JavaScript における new 演算子の動作は大まかにいって以下のとおりである。(new F() とした場合。)

  • 1:新しいオブジェクトを作る。
  • 2:1 で作ったオブジェクトの [[Prototype]] 内部プロパティ (__proto__ プロパティ) に F.prototype の値を設定する。
    • F.prototype の値がオブジェクトでないのなら代わりに Object.prototype の値を設定する。
  • 3;F を呼び出す。このとき this の値は 1 で作ったオブジェクトとし、引数には new 演算子とともに使われた引数をそのまま用いる。
  • 4:3 の返り値がオブジェクトならそれを返す。そうでなければ 1 で作ったオブジェクトを返す。

ここで「オブジェクトである」というのはプリミティブ値 (文字列、数値、真偽値、undefined 、null) ではないということだ。
new String("string") 、new Number(123) 、new Boolean(true) はオブジェクトだが "string" 、123 、true はオブジェクトではない。

というわけで。。

コード見ながらnewの動きを整理
var Class = function(){}
Class.prototype = {
	hoge : 'piyo'
};
var obj = new Class();		//(2-1)
alert(obj.hasOwnProperty(`hoge`));	//(2-2)false
obj.hoge = 'fuga';			//(2-3)
alert(obj.hasOwnProperty(`hoge`));	//(2-4)true
  • 1: (2-1)でnewすると、新しいオブジェクト{}を作る。
  • 2: 作ったオブジェクトの__proto__にClassのprototypeが格納している参照を格納。

    ここでClass.prototypeがオブジェクトでないなら、Object.prototype(の参照)を格納。ここではClass.prototypeはオブジェクト{hoge:'piyo'}なのでこれの参照を格納する。

  • 3: 関数Class()を呼び出し。

    ここでthisには新しく作ったオブジェクト{}への参照が格納される。newした時に与えられた引数でClass()を実行

  • 4: 全てが終わったら、newはオブジェクト{}への参照を返す。

という感じ?

prototypeと__proto__は分けて考えたほうが良いらしいです。前者は関数オブジェクトの雛形。後者は作られたオブジェクトが実行時に参照するときのもの。なるほど。(なので、上でobj.prototypetと書いてたのは、obj.__proto__と記述したほうが良いのかな。)

図で示すとわかりやすいprototype

こんなコード。ほげほげうるさいけれど我慢w

var Class = function(){};

Class.prototype = { prop : 'hogehoge' };
var hoge = new Class();			//(3-1)
alert(hoge.prop);		//hogehogeと出力

Class.prototype = { prop : 'piyopiyo' };
var piyo = new Class();			//(3-2)
alert(piyo.prop);		//piyopiyoと出力

var fuga = new Class();			//(3-3)
Class.prototype = { prop : 'fugafuga' };
alert(fuga.prop);		//(3-5)piyopiyoと出力
fuga.__proto__.prop = 'piyofuga';

alert(hoge.prop);		//hogehogeと出力
alert(piyo.prop);		//piyofugaと出力
alert(fuga.prop);		//piyofugaと出力

の、オブジェクトの様子を図示してみた。

黒線がnewによるオブジェクトの作成。各色の線が各参照先を示す。数字はオブジェクト作成やチェーンが作られる順番。

(3-1)でvar hoge作成時に、Class.prototypeが指しているのは、{ prop : 'hogehoge' }で、hoge.__proto__はこれを参照する。
var piyoも同様(3-2)

ここで、var fugaの作成時(3-3)に、Class.prototypeが指しているのは{ prop : 'piyopiyo' }のままなので、fuga.propは'fugafuga'でなく、'piyopiyo'となる(3-5)ということ。
((3-4)で{ prop : 'fugafuga' }を作っているが、これを参照するつながりはできていない。)

その後、fuga.__proto__.propの値を変更すると、{ prop : 'piyopiyo' }オブジェクトのpropプロパティを変更することになる。すると、fugaだけでなく、piyoの__proto__も同じオブジェクトを指しているので、piyo.propも'piyofuga'になる。ということ。

ちなみに、(3-4)でClass.prototypeは{ prop : 'fugafuga' }を参照するようになるが、このあとnewでオブジェクトが作られないかぎり{ prop : 'fugafuga' }を__proto__で参照するオブジェクトは存在しないことになる。
大事なのは、{}も、(どこかよくわからん場所に作られる名前の無い)オブジェクトなんだということか。

だいぶわかってきた!

でも、サンプルコードがわかりにくかったか。。あとは実践だな。

参考

(勝手に)参考にさせていただいてありがとうございます。

関連記事