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章のクロージャから難しくなってきそうです。

Secret of the JavaScript Ninja 2章を読んだ

d:id:ojimacと定期的に読んでいくことになった。
一緒に読みたい!という人がいれば気軽にTwitter(@cheesepie)で声かけてください!


2章はデバッグの基本(ロギングとブレークポイント)とテストケースについて。

ロギングはまあconsole.logをクロスブラウザ化して使いましょう、ブレークポイントはブラウザのコンソール使ってやっていきましょうてな具合で。


テストケースは3つのポリシーを守るべし。

  • テストは高い再現性を持つべし
  • テストは出来る限りシンプルにするべし
  • テストはテスト同士が依存しないように最小単位のテストに分割するべし


単体テストフレームワークとして、QUnit/YUITest/JSUnitが紹介されています。
JSUnitはメンテされてないので、モダンブラウザでのテストにはちょっと不安が。
個人的にはGoogle Closure Libraryのテストフレームワークが一番好みです!


で、既存のテストフレームワークを使った方がよさげなんだけど、どういう動きをしているのかを理解しておくのはいいことだよね。
ということで、テストフレームワークの基本要素となるAssertion/Test Groups/Asynchronous testingの3つがどういう仕組みになっているかをシンプルなコード例とともに説明しています。
それぞれ40行くらいで書けちゃうんだぜとはResig殿の弁。

非同期テストのサンプルが分かりやすかった。

      (function() {
        var queue = [], paused = false, results;

        this.test = function(name, fn) {
          queue.push(function() {
            results = document.getElementById("results");
            results = assert( true, name ).appendChild(
              document.createElement("ul") );
            fn();
          });
          runTest();
        };

        this.pause = function() {
          paused = true;
        };

        this.resume = function() {
          paused = false;
          setTimeout(runTest, 1);
        };

        function runTest() {
          if ( !paused && queue.length ) {
            queue.shift()();
            if ( !paused ) {
              resume();
            }
          }
        }

        this.assert = function assert( value, desc ) {
          var li = document.createElement("li");
          li.className = value ? "pass" : "fail";
          li.appendChild( document.createTextNode( desc ) );
          results.appendChild( li );
          if ( !value ) {
            li.parentNode.parentNode.className = "fail";
          }
          return li;
        };
      })();

      window.onload = function() {
        test("Async Test #1", function() {
          pause();
          setTimeout(function() {
            assert( true, "First test completed" );
            resume();
          }, 1000);
        });

        test("Async Test #2", function() {
          pause();
          setTimeout(function() {
            assert( true, "Second test completed" );
            resume();
          }, 1000);
        });
      };

このテストがどういう流れで処理されるかというと・・・

  1. Async Test #1のtest()が実行される
  2. キューにAsync Test #1のtest()の引数で渡された関数が入る
  3. runTest()
  4. キューから先ほど入れられた関数を取り出し実行する
  5. pausedをtrueにする
  6. Async Test #2のtest()が実行される
  7. キューにAsync Test #2のtest()の引数で渡された関数が入る
  8. runTest()
  9. !pausedがfalseを返すので何もしない
  10. Async Test #1のsetTimeoutで指定された関数が実行される
  11. "First test completed"をHTMLに書き出す
  12. pausedをfalseにし、runTest()
  13. キューからAsync Test #2のtest()の引数で渡された関数を取り出し実行する
  14. pausedをtrueにする
  15. Async Test #2のsetTimeoutで指定された関数が実行される
  16. "Second test completed"をHTMLに書き出す
  17. pausedをfalseにし、runTest()
  18. キューには何も入っていないので終了

キューとpause()とresume()を使うことで、非同期であっても必ず「#1のテスト→#2のテスト」という順番でテストが実行されるようになる。
これで3つのポリシーでも挙げられていた「テストは高い再現性を持つべし」を実現できるということか。


いやー勉強になります。
最後まで読んでみないと分かりませんが、Secret of the JavaScrpt Ninjaはサイ本と並んでオススメできるかも。
書籍の発売日は8月なのですが、PDF版は先行して購入できます。
PDF版もまだ14章までしかありませんが、章が追加されたり更新されるたびに新しいPDFを受け取れるようになっています。

Secrets of the JavaScript Ninja

Secrets of the JavaScript Ninja

XMLHttpRequestのステータスコードが0になるケース

XMLHttpRequestステータスコードが0になるケース。

1. プロトコルftpやfileなどhttp以外の場合
  →例えば、ローカルでhtmlファイル開いて実行したりするとステータスは"0"になる

2. Webサーバーがリクエストを送る時点で落ちていた場合
  w3.orgに仕様として、0を指定することとある
  http://www.w3.org/TR/XMLHttpRequest/#error-flag

3. 古いOperaでは「204/304/504」が0として扱われる
  http://leaf.argyr.net/javascript/xmlhttprequest-response/

2のケースが起きた場合、判定する術がない。。。
responseTextが空?いや200で空のときもありえるし(そもそも204 No Content返すべきだろうけど)。

なので、「200/204/304」なら成功、それ以外(0も含む)はエラーとして扱うのがいいかな。

週末

土曜

知人と3人で前から行きたかったガパオ食堂に行ってきた。
裏道にあるので、6年ぶりに機種変した携帯についてる地図アプリが無かったら絶対辿り着けない。。。
最新テクノロジ万歳!

春雨サラダとトムヤンクンの時点で辛さで舌がマヒしかけたんだけど、味は文句無しでした。
それにしてもタイビールが美味しくてびっくり。
シンハは名前だけ知ってたけど、プーケットって地名まんまじゃん!でもフルーティで美味しかった。

次回はハイスコアキッチンに行きたい。

日曜

左肩に何かが取り憑いてるので、お祓いも兼ねてマッサージへ。
「お客さん、ケツが凝ってますね!」ってあれ左肩は。。。
カッチカチやぞ!と返したりはしません。

ルノアールでひたすらGoogle Closureのソースリーディング。
Componentの使い方とグラフ周りいろいろ。グラフ周りはサーバで画像作るかCanvasでやるか悩むー。
API化するとか考えるとデータだけもらってCanvasでプロットするのがいいのかも。


ということで来週は仕様決めとデザインと実装と、いろいろ頑張る。

Yii

なんつー強引な。

任天堂が「Zii」「Oii」「Pii」「Qii」「Uii」「Yii」を商標登録していた

2006年に既に登録してたのね。キーボード配列的に「Qii」はありえるかも。


PHPフレームワークの方も忘れないであげてください。
Yii PHP Framework: Best for Web 2.0 Development


音楽プレーヤーの方も。
Zii.com