JavaScriptのnew

JavaScriptのnewは危険だよんとCrockfordの本にもありますが、あんまり意味がわかってなかったのですこし調べてみました。

そしたら
John Resig - Simple “Class” Instantiation
に全て書いてありました(英語に自信無いけどたぶん)。

単純にやると以下のようになります。

function User(first, last){
    this.name = first + " " + last;
}
var user = new User("John", "Resig");
user.name // "John Resig" 

しかしこの方法だとnewを忘れた場合に問題があります。thisにグローバルオブジェクトがセットされるためグローバル変数を上書きしてしまいます。

var user = User("John", "Resig");
user // undefind
name // "John Resig"

これを防ぐために以下のようにすることもできますが、もっと汎用的なものがjqueryには用意されています。

function User(first, last){
  if ( this instanceof User ) {
    this.name = first + " " + last;
  } else
    return new User(first, last);
}

それがmakeClass関数です。なんか黒魔術的ですね。makeClassは無名関数を返します。

function makeClass(){
  return function(args){
    if ( this instanceof arguments.callee ) {
      if ( typeof this.init == "function" )
        this.init.apply( this, args.callee ? args : arguments );
    } else
      return new arguments.callee( arguments );
  };
}

使い方はこんな感じ。

var User = makeClass();
User.prototype.init = function(first, last){
  this.name = first + " " + last;
};
var user = User("John", "Resig");
user.name // => "John Resig" 

User("John", "Resig")が呼ばれると(newが無いので)makeClassのelseに入ります(argumentsは["John", "Resig"])。そしてthis.init.applyに入ります。つまりmakeClassは2回呼ばれる。
this.init.apply( this, args.callee ? args : arguments )はthis.init.apply( this, args)となります(argsは["John", "Resig"])。

new User("John", "Resig")とすると結果は同じだけどいきなりmakeClassのthis.init.applyに入る。つまりmakeClassが呼ばれるのは1回だけ。
this.init.apply( this, args.callee ? args : arguments )はthis.init.apply( this, arguments)となる(argsは"John",argumentsは["John", "Resig"])。

ややこしいですね。