@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Backbone.Router

Backbone.Router を読んだ。ざっと眺めただけ。

http://documentcloud.github.com/backbone/docs/backbone.html

  • そもそも Router とはなんぞや?というと、クライアントサイドでもサーバサイド同様に URL でもって処理変えましょうね、という。 History API が使えれば pushState を使い、でなければ hash fragment を使う。そこのところを透過的に扱える機能を提供、ということ。
    route : function(route, name, callback) {
      Backbone.history || (Backbone.history = new Backbone.History);
      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
      Backbone.history.route(route, _.bind(function(fragment) {
        var args = this._extractParameters(route, fragment);
        callback.apply(this, args);
        this.trigger.apply(this, ['route:' + name].concat(args));
      }, this));
    },
  • ここはどうなってるかというと:
    • Backbone はグローバルな名前空間にある Backbone オブジェクトで、 history プロパティを Backbone.history インスタンスで初期化する
    • 第一引数で渡された route が正規表現オブジェクトでなければ正規表現オブジェクトにする
    • Backbone.History.route を呼び出す。引数は↑で正規表現オブジェクトであることが保証された route と、第二引数にあたる部分が長いけど _.bind() で囲われた部分を丸ごと渡している
    • _.bind(function, object, [*arguments]) を呼び出しててこれの第一引数 function にあたるのが function(fragment) { ... } までで、第二引数は this なので new Backbone.Router.extend したインスタンス自身?
    • このでかい (function(fragment) { ... }) のなかで callback つまり route にマッチしたとき実行して欲しい関数が呼ばれる
    • this つまり new Backbone.Router.extend したインスタンス自身を引き回して callback.apply に渡してるのは、たぶん callback の定義は extend({ ... }) の中に書きたくて、それだと this.route('/foo/bar', 'foobar', function() { ... }) みたいになるから (この this が new Backbone.Router.extend したインスタンス自身なので) this が狂っちゃうとうまくないから、だと思う
    • Backbone.History.route は内部の handlers という配列に route と callback のペアを追加していくだけで、 Backbone.History.loadUrl が呼ばれるときにさっき渡した callback 関数も実行される
    • Backbone.History.route は (route, callback) を受け取るので _.bind() の返す関数オブジェクトを渡してて正解
    • Backbone.Router.route の第二引数に渡される name は trigger すべきイベント名を決めるために使われる
  • ということで書いてる自分にすらどうなってるか理解した上で書けてるのかはなはだ怪しいけどこんなもんにしとく
  • navigate も Backbone.History.navigate に丸投げしてるだけ
  • _bindRoutes は「後に定義された route を採用する」振る舞いのために unshift で空の配列に逆順で詰めなおしてから Backbone.Router.route で追加しなおしてる。 _bindRoutes は new Backbone.Router.extend({ ... }) したときに実行されるので、 extend に route の初期値を渡したときのためにある
  • _routeToRegexp はいろいろがんばって正規表現にしてるけど読んでない。あと最初のほうの capture 用っぽい正規表現も読んでない
  • _extractParams はどう動くのかよくわからん・・・たぶん /:id みたいなルートに対して /17 みたいな URL を渡すと [17] を返してくれる、んだと思うけど

というわけで、 Backbone.History がこれよりもっと重要っぽいことがわかった。こちらもざっと眺めてみた感じ、ブラウザ間の差異 (iframe 使うとか) とか、勉強になりそうな感じだし一つの関数のボリュームも大きそうだったけどまた別の機会に。

ルートの追加のところとかなんか見覚えあると思ったら CPAN モジュールの Router::Simple とかもこんな感じの使い方してた気がする。内部の実装も眺めたけどどのくらい(似てる|違う)のかはよくわからず。でもこういうのってこうやって作るんだーというのがぼんやり把握できただけでよしとする。