prototypeとリテラルの、まとまりのないまとめ
リテラルでprototype書いたら - ynsynsのブログの続き。
function Y(){}; Y.prototype={a:'a'}; var y=new Y(); console.log(y.__proto__); // Object { a="a"} console.log(y.constructor); // Object()
何故コンストラクタがObjectになるか。
Y.prototype={a:'a'};
これが何をしているかが問題。
リテラル代入は、いきなりプロパティを設定しているのではなく、
いったん空オブジェクトをnewされてからプロパティを入れ直しているということ。
つまり
Y.prototype={}; //いったん空オブジェクトを生成したことになる。なるほどnew Objectと同じ! Y.prototype.a='a'; //そのあとで、プロパティを設定。
暗黙的にこの2段階の処理がされている。
new Objectされてるのと同義なら、コンストラクタはObjectになるわけだそりゃ。
脱線して、var宣言の例
var x=0; //初期化と同時に値をセットしたい
しかし実際の処理は
var x=undefined; //コンパイル時はスコープのトップレベルでundefinedで定義されたことになる。 x=0; //続いて値のセット
昔それ周りのこと書いてた。
2010-07-27 - ささいなoutput
話を戻す。
prototypeの場合は厳密にはvar宣言とは異なるかもしれないが、
変数(プロパティ)に値をセットするというのは、
いったん内容が消去(代入されるオブジェクトのコンストラクタに初期化)されてから、新たに書き直されるという動きとなる。
従って、書き直される以前に持っていた属性(適切な言い方ではないかも)が消えてしまう。
パターン実験
function Oya1(){}; Oya1.prototype={a:1}; //リテラルで初期化 var ko1=new Oya1; console.log(ko1.constructor, ko1.a); Oya1.prototype={a:2}; //リテラルで更新 console.log(ko1.constructor, ko1.a); function Oya2(){} Oya2.prototype.a=1; //プロパティで初期化 var ko2=new Oya2; console.log(ko2.constructor, ko2.a); Oya2.prototype={a:2}; //リテラルで更新 console.log(ko2.constructor, ko2.a); function Oya3(){} Oya3.prototype.a=1; //プロパティで初期化 var ko3=new Oya3; console.log(ko3.constructor, ko3.a); Oya3.prototype.a=2; //プロパティで更新 console.log(ko3.constructor, ko3.a); function Oya4(){} Oya4.prototype={a:1}; //リテラルで初期化 var ko4=new Oya4; console.log(ko4.constructor, ko4.a); Oya4.prototype.a=2; //プロパティで更新 console.log(ko4.constructor, ko4.a); /*結果 Object() 1 Object() 1 Oya2() 1 Oya2() 1 Oya3() 1 Oya3() 2 Object() 1 Object() 2 */
Oya3のが最もクリーンなやり方と言える。
constructorの部分について
Oya2,Oya3のように、prototypeのプロパティに設定している場合はconstructorは破壊されず継承している。
Oya1,Oya4のようにリテラル代入した場合は、本来のconstructorが破壊されObjectになる。
プロパティ値を更新した後の挙動
Oya3,Oya4のようにプロパティに変更を与えた時には値が更新される。
Oya1,Oya2のようにリテラルで更新すると、以前の値には影響しない。
Oya2に注目
function Oya2(){} Oya2.prototype.a=1; //プロパティで初期化 var ko2=new Oya2; console.log(ko2.constructor, ko2.a); // >>>Oya2() 1 Oya2.prototype={a:2}; //リテラルで更新 console.log(ko2.constructor, ko2.a); // >>>Oya2() 1 // 既に作ったインスタンスに影響しない! Oya2.prototype.b=1; //追加してみる console.log(ko2.constructor, ko2.a, ko2.b); // >>>Oya2() 1 undefined // 追加されない! けどnewされた時点の値は保持している。 var ko20=new Oya2; // 別のインスタンス作る console.log(ko20.constructor, ko20.a, ko20.b); // >>>Object() 2 1 // constructorが破壊されている!
ko2ではリテラル上書きされた後も、直前の内容を保持している、かつ"追加が不可能"になる。
ko20ではリテラル上書きされたあとのインタンスで、本来のconstructorは破壊され無名Objectで再定義されている。
結論としては、
constructorが破壊されたり、既存のインスタンスへの継承が絶たれたり、
jsのダイナミック性という言語的特徴を否定することにもなるし、
prototypeをリテラルで書くのはやはりよくないという認識に至る。
prototypeの定義は面倒でもプロパティごとでセットするか、applyするかがよろしいかと思う。
意図的に使う高等テクニックもあるのかもしれないけど。
最後に、Oyaとかkoとか芸の無い名前でテストして、途中で残念な気持ちになった。