Backbone.Router のルートの初期化および navigate() の動作のコードを見てみる
Backbone.Router のコードでルートの初期化部分と navigate() による動作が気になったのでコード上で確認してみる.
Backbone.Router の使い方は下記の様な感じ.
var AppRouter = Backbone.Router.extend({ initialize: function() { _.bindAll(this, 'fuga', 'fuga2'); // ここでは意味ないけど }, routes: { 'hoge': 'fuga', 'hoge2/:param': 'fuga2' }, fuga: function() { console.log('fuga'); }, fuga2: function(param) { console.log('fuga2 ' + param); } });
見てたコードは Backbone.js 1.1.2
流れ概要
- コンストラクタ
- navigate()
- ハンドラを登録した順に確認していって最初に一致したもののハンドラを実行する
コード追跡
コンストラクタで routes の中身を取ってきて _bindRoutes() でバインドしてる.
1217 var Router = Backbone.Router = function(options) { 1218 options || (options = {}); 1219 if (options.routes) this.routes = options.routes; 1220 this._bindRoutes(); 1221 this.initialize.apply(this, arguments); 1222 };
_bindRoutes では routes の後ろから順に route() を呼び出す.
1355 // Bind all defined routes to `Backbone.history`. We have to reverse the 1356 // order of the routes here to support behavior where the most general 1357 // routes can be defined at the bottom of the route map. 1277 _bindRoutes: function() { 1278 if (!this.routes) return; 1279 this.routes = _.result(this, 'routes'); 1280 var route, routes = _.keys(this.routes); 1281 while ((route = routes.pop()) != null) { 1282 this.route(route, this.routes[route]); 1283 } 1284 },
route() では events のキーになっていた部分を RegExp にしておく. (後述するが navigate() のタイミングで正規表現として評価される.) fragment には pushState が有効か無効かで変わるが true の場合は多分ルート以下のパスが入る. route() の呼び出しは routes の逆順で Backbone.histroy.route() では配列に unshift() で突っ込んでるので routes の前にかいてあった順が優先順位になる?
1244 route: function(route, name, callback) { 1245 if (!_.isRegExp(route)) route = this._routeToRegExp(route); 1246 if (_.isFunction(name)) { 1247 callback = name; 1248 name = ''; 1249 } 1250 if (!callback) callback = this[name]; 1251 var router = this; 1252 Backbone.history.route(route, function(fragment) { 1253 var args = router._extractParameters(route, fragment); 1254 router.execute(callback, args); 1255 router.trigger.apply(router, ['route:' + name].concat(args)); 1256 router.trigger('route', name, args); 1257 Backbone.history.trigger('route', router, name, args); 1258 }); 1259 return this; 1260 },
多分下記のところで名前付きパラメータは正規表現のグループにされる.
1288 _routeToRegExp: function(route) { 1289 route = route.replace(escapeRegExp, '\\$&') 1290 .replace(optionalParam, '(?:$1)?') 1291 .replace(namedParam, function(match, optional) { 1292 return optional ? match : '([^/?]+)'; 1293 }) 1294 .replace(splatParam, '([^?]*?)'); 1295 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); 1296 },
trigger が true ならば loadUrl() がよばれる
1494 navigate: function(fragment, options) { 1530 if (options.trigger) return this.loadUrl(fragment);
loadUrl() では handlers に格納されているルートのうち最初に javascript の文字列における test が true を返すハンドラに引き数を与えて呼び出す.
1477 loadUrl: function(fragment) { 1478 fragment = this.fragment = this.getFragment(fragment); 1479 return _.any(this.handlers, function(handler) { 1480 if (handler.route.test(fragment)) { 1481 handler.callback(fragment); 1482 return true; 1483 } 1484 }); 1485 },
getFragment() では pushState が有効か無効かでパスを返すかハッシュを返すかを変える.
1370 getFragment: function(fragment, forcePushState) { 1371 if (fragment == null) { 1372 if (this._hasPushState || !this._wantsHashChange || forcePushState) { 1373 fragment = decodeURI(this.location.pathname + this.location.search); 1374 var root = this.root.replace(trailingSlash, ''); 1375 if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); 1376 } else { 1377 fragment = this.getHash(); 1378 } 1379 } 1380 return fragment.replace(routeStripper, ''); 1381 },