Secret of the JavaScript Ninja 3章を読んだ

2章に続いて3章を読み合わせました。

ちなみに、2章で使ったassert()がほぼ全てのコードで使われているので、サンプルコードを試したい方はライブラリ化しといた方がいいと思われます。


3章は関数について。

JavaScript関数型言語なんだよ、関数はファーストクラスでオブジェクトみたいに扱えるよ。
関数の中でも匿名関数(anonymous function)は最重要だよ。理解できたら綺麗で再利用しやすいコードが書けるようになると思うよ!的な。


で、匿名関数にかなりのページが割かれてます。
匿名関数というのは無名関数とも呼ばれますが、この本では「名前を持つ必要がない関数」と定義しているようです。
anonymousってあるけど、namelessとした方がいいのかな・・
オブジェクトのプロパティやイベントハンドラ、コールバックなんかでいきなり定義するfunctionはどれも匿名関数。

さりげなく再帰のコード例で「arguments.callee」が出てきました。
匿名関数で再帰をするときは「arguments.callee」使うと綺麗に書ける。


その後は、関数にもオブジェクトみたいにプロパティを追加できるよとか、関数内のコンテキストオブジェクト(this)を書き換えたければ「call() or apply()」を使えばできるよという話が続きます。


分かりやすかったのが、"Fake Array Methods"の件。
lengthプロパティを追加したオブジェクトにはArrayメソッドを利用できる。

var elems = {
  length: 0,
  add: function(elem) {
    Array.prototype.push.call(this, elem);
  }
};

例えばオブジェクトに要素を突っ込みたいときなど、lengthプロパティを付ければ、配列用のメソッドをthisを書き換えることで利用できるようになって便利という話。


今回一番勉強になったのは、JavaScriptでのオーバーロードのやり方。

function addMethod(object, name, fn){
  var old = object[ name ];
  object[ name ] = function(){
    if ( fn.length == arguments.length )
      return fn.apply( this, arguments )
    else if ( typeof old == 'function' )
      return old.apply( this, arguments );
  };
}

function Ninjas(){ 
  var ninjas = [ "Dean Edwards", "Sam Stephenson", "Alex Russell" ];

  addMethod(this, "find", function(){
    return ninjas;
  });
  addMethod(this, "find", function(name){
    var ret = [];
    for ( var i = 0; i < ninjas.length; i++ )
      if ( ninjas[i].indexOf(name) == 0 )
        ret.push( ninjas[i] );
    return ret;
  });
  addMethod(this, "find", function(first, last){
    var ret = [];
    for ( var i = 0; i < ninjas.length; i++ )
      if ( ninjas[i] == (first + " " + last) )
        ret.push( ninjas[i] );
    return ret;
  });
}

var ninjas = new Ninjas();
assert( ninjas.find().length == 3, "Finds all ninjas" );
assert( ninjas.find("Sam").length == 1, "Finds ninjas by first name" );
assert( ninjas.find("Dean", "Edwards").length == 1, "Finds ninjas by first and last name" );
assert( ninjas.find("Alex", "X", "Russell") == null, "Does nothing" );

addMethod()を呼ぶたびに、同じ名前で追加されている関数をold変数に突っ込んでいってます。
old変数の中身を見ると・・・

  • 1回目のaddMethod()時のold変数
undefined
  • 2回目のaddMethod()時のold変数
var old = function(){
  if ( fn.length == arguments.length ) // fnは1回目で登録された匿名関数
    return fn.apply( this, arguments )
  else if ( typeof old == 'function' ) // oldは'undefined'なのでスルー
    return old.apply( this, arguments );
};
  • 3回目のadd()時のold変数
var old = function(){
  if ( fn.length == arguments.length ) // fnは2回目で登録された匿名関数
    return fn.apply( this, arguments )
  else if ( typeof old == 'function' ) // oldは2回目のaddMethod()時のold変数
    return old.apply( this, arguments );
};

引数の数をチェックして、falseならそれより前に登録された関数でもっての引数の数をチェックして・・・
という感じでオーバーロードを実現しています。

おおー確かにこうやればできるね!奥が深い。
ただ、関数呼び出しのオーバーヘッドが大きいので、パフォーマンスが求められる場合は注意してねとのこと。


最後は関数かどうかのチェック方法について。
「Object.prototype.toString.call(arg)」でおk。jQueryもそうなってますしね。
これは既にライブラリ化済みだぜ!


サンプルコードを見る限りでは、ここまでが序の口で4章のクロージャから難しくなってきそうです。