面倒くさいJavascriptの整理−var , this , prototype編
サンプルで動作確認
1.thisの動作
function ClassA( name ) { this.name = name; ClassA.prototype.getName = function() { return this.name; } } function ClassB( name ) { this.name = name; ClassB.prototype.getName = ClassA.prototype.getName; } var classA = new ClassA( "classA" ); var classB = new ClassB( "classB" ); alert( classA.getName() ); alert( classB.getName() );結果:ClassA:classA,ClassB:classB
new演算子はこのように振舞うようにするためのもの。
thisは実行時のオブジェクトを指すように振舞う。
そして、イベントハンドラでは普通にインスタンスメソッドを渡すとthisはelementを指すのでホゲる。
だから、イベントハンドラに素直にthisを書くとelementを指してしまうので何とかする必要があることが分かると思う。
また、例ではnewの動作を分かりやすくするためにClassBのprototypeにClassAのprototypeを代入しているが、この場合、ClassBがClassAに依存する形になっているので、不自然だが無視。
2.varの動作
function ClassA( name ) { var Name = name; ClassA.prototype.getName = function() { return Name; } } function ClassB( name ) { var Name = name; ClassB.prototype.getName = ClassA.prototype.getName; } var classA = new ClassA( "classA" ); var classB = new ClassB( "classB" ); alert( classA.getName() ); alert( classB.getName() );結果:ClassA:classA,ClassB:classA
varとthisの動作の違いを理解するべし。
特にvarは関数が宣言された位置が参照スコープの基準になる点が重要。
追記しておくと、参照スコープはfunction区切り。
3.仮引数と内部関数の動作
function ClassA( name ) { ClassA.prototype.getName = function() { return name; } } function ClassB( name ) { ClassB.prototype.getName = ClassA.prototype.getName; } var classA = new ClassA( "classA" ); var classB = new ClassB( "classB" ); alert( classA.getName() ); alert( classB.getName() );結果:ClassA:classA,ClassB:classA
ついでに内部変数varではなくて仮引数の場合も確認しておく。
仮引数についても、関数の内部に宣言された場合は同じ動作をする。
なぜか、prototypeに無名関数を代入する方法ではなく、以下のような直接宣言する方法は
function ClassA.prototype.getName()
IEでは問題ないがFFでは構文エラーになった。
普通はprototypeの宣言はコンストラクタ関数の外部に出すのであまり良いサンプルではないのでが、次のサンプルはprototypeの理解にも役立つように思える。
4.仮引数と内部関数とprototypeの動作
function ClassA( name ) { ClassA.prototype.getName = function() { return name; } } function ClassB( name ) { ClassB.prototype.getName = ClassA.prototype.getName; } var classA = new ClassA( "classA" ); var classB = new ClassB( "classB" ); var classAA= new ClassA( "classAA" ); alert( classA.getName() ); alert( classAA.getName() ); alert( classB.getName() );結果:classA:classAA,classAA:classAA,classB:classA
まず、classAとclassAAの結果に注目。
理解しやすいが、ClassAはインスタンスを作成する度にprototype.getName()を書き換える点を理解する。
結果、サンプルでclassA.getName()が呼び出されている部分でのgetNameの実態は、classAAインスタンスを作成したときのgetName関数ということになる。クラスClassAのインスタンスclassAもclassAAも同じprototypeを共有しているためこのようなことが起こる。これがprototypeの振る舞い。一方で、このようにコンストラクタ関数の中にprototypeを宣言する危険も示したつもり。次にclassBの結果にも注目。
ちょっと1つのサンプルに欲張りすぎた。ClassBのコンストラクタ関数で仮引数nameはもちろん参照されない。
また一見すると、ClassBのprototype.getNameはClassAのprototype.getNameを参照しているように思われるがそうではなく、この場合classBのgetNameはclassAインスタンス作成時のgetNameの無名関数を参照している。
だからその後、classAAのgetNameでClassAのprototype.getNameが上書きされても影響を受けない。
その上、classAインスタンス作成時に渡された引数"classA"もClassB.prototype.getNameから参照されているため解放されていない。クロージャを作成するときにはこの性質が利用される。
5.まとめ
function ClassA( name ) { var cnt=0; ClassA.prototype.getName = function() { return name += (cnt++); } } function ClassB( name ) { var cnt=0; this.getName = function(){return name += (cnt++); }; } var classA = new ClassA( "classA" ); var classAA = new ClassA( "classAA" ); alert( classA.getName() ); alert( classAA.getName() ); var classB = new ClassB( "classB" ); var classBB = new ClassB( "classBB" ); alert( classB.getName() ); alert( classBB.getName() );結果:classA:classAA0,classAA:classAA01,classB:classB0,classBB,classBB0
これを見て思い出せ自分!
この動作確認だけを考えるとprototypeなんか使わない方が懸命だと思ってしまうが、
継承を考えると。。。そうとも言えない気がする。
また、大量にインスタンスを作るときもコンストラクタ関数がでかい分パフォーマンスに影響すること必然。