JavaScriptにクラスはない。だがクラスっぽいものはできる。
というわけでnew式についての覚書。
new式ってなんぞ
new演算子に続けて関数名を書けば、その関数をコンストラクタとしたオブジェクトを生成する。
function Constructor (){
this.x = 'Heloo World!!';
}
var obj = new Constructor();
console.log(obj.x); //Hello World!!
無事オブジェクトobjが生成されました。
newは関数であればなんでもコンストラクタとして使える。逆に関数以外の型に対してはTypeErrorを返す。
/* メソッドからでも生成できる */
var method = {
fn : function(){
this.str = 'ネゴトワ・ネテイエ';
}
};
var obj = new method.fn();
console.log(obj.str); // ネゴトワ・ネテイエ
/* 無名関数だって使えます */
var anonymous = new function(){
this.str = 'しまじろうとそのお友達が許すものか';
};
console.log(anonymous.str); // しまじろうとそのお友達が許すものか
/* でも他の型ではダメらしい */
var arr = [];
var obj = new arr; // TypeError: object is not a function
new式の動作は以下の通り。
空のオブジェクトを生成され、コンストラクタ関数内のthisに、先ほど生成したオブジェクトの参照を渡す。
thisで参照されているオブジェクトに、コンストラクタ関数のプロトタイプを継承させる。
thisで参照されているオブジェクトにプロパティとメソッドを追加する。
コンストラクタ関数の最後で、thisで参照されているオブジェクトを返す。returnで他のオブジェクトを指定すればそのオブジェクトを返す(returnで返せるのはオブジェクトのみ。オブジェクト以外を指定するとthisで参照されているオブジェクトを返す)。
つけ忘れるとどうなる?
new式はいろいろと問題がある(ってグッドパーツのおっちゃんが言ってた)。もっともよくあげられるのはインスタンス時に付け忘れた場合のえらいこっちゃさだ。
/* newを付け忘れたパターン */
var Constructor = function(){
this.message = 'こんにちは!';
}
var obj = Constructor();
console.log(obj.message); // TypeError: Cannot read property 'message' of undefined
console.log(message); // こんにちは!
newがなかった場合、コンストラスタ関数内のthisはグローバルオブジェクトを参照するため、
this.messageはグローバル空間にmessageを定義してしまう。
さらに、コンストラクタ関数は本来ただの関数(構文的には間違ってない)ため、エラーも吐かない。名前空間は汚染するわアラートも出さないわというなんとも困ったちゃんで、bad partsに分類されるのもいたしかたな、という感じがします。
つけ忘れにこういった問題点を解決するためにいろいろなパターンがあるのでまとめてみた。
/* newを強制するパターン */
var Constructor = function(){
// Constructorのインスタンスでなければ強制的にnewを施す。
if(!(this instanceof Constructor)){
return new Constructor();
}
this.message = 'こんにちは!';
}
var obj1 = Constructor();
var obj2 = new Constructor();
console.log(obj1.message); // "こんにちは!"
console.log(obj2.message); // "こんにちは!"
この書き方ならnewをつけ忘れても問題はない。
しかし、new付き・newなしのインスタンスの混在をソースコード上に許容するのはどうにもなんだか気持ちが悪く、応急措置的な対策である感は否めない。
気持ち悪いなら仕方ないので、そもそもnewを義務付けるようにすべきである。
エラーを吐かないのが問題なら吐くようにすればよい。newの代わりに例外を投げるのである。
/* newを忘れると例外を投げるパターン */
var Constructor = function(){
// Constructorのインスタンスでなければ例外をスロー。
if(!(this instanceof Constructor)){
throw Error('newがないでヤンス!!');
}
this.message = 'こんにちはでヤンス!';
}
var obj = Constructor(); // Error: newがないでヤンス!!
他にもprototypeに丸投げするパターンもあるらしい。
/* protetypeに丸投げするパターン */
var Constructor = function() {
this.initialize.apply(this,arguments);
}
Constructor.prototype.initialize = function(){
this.message = 'こんにちはでヤンス!'
};
var obj = Constructor(); // TypeError: Cannot read property 'apply' of undefined
newを忘れると、applyを実行するためのthis.initializeが見つからないためTypeErrorが返る。
ただ、この書き方の場合、もしグローバル空間に変数initializeが存在すればとうぜん事態は異なる。
/* protetypeに丸投げするパターン(失敗例) */
var initialize = function(){ this.message = 'またあったなでヤンス!'; };
var Constructor = function() {
this.initialize.apply(this,arguments);
}
Constructor.prototype.initialize = function(){
this.message = 'しつこいでヤンス!'
};
var obj = Constructor();
console.log(obj.message); // TypeError: Cannot read property 'message' of undefined
console.log(message); // 'またあったなでヤンス!'
newがなかった場合、this.initializeのthisはやはりグローバルオブジェクトを参照するため、
this.initializeはグローバル空間に定義されたinitializeを参照してしまう。
initializeの存在に注意すればいいように思えるが結局は運用に頼った書き方になるため、本末転倒になってしまう。new忘れの解決策を主目的とするにはあまり好ましくない書き方に思える。