大家用backbone、angular,可能都习惯了内置的路由,这两个框架的路由都是非常优秀的,强大而简单。
客户端(浏览器)路由原理其实比较简单,其实就是监听hash的变化。
在之前的架构探讨中,说到director.js这个路由类库不好使,那么,在这一篇,我们尝试自行实现一个简洁而且非常好使的路由类库。
原理先介绍,无非几个步骤:
- 建立配置表(字符串路径和函数的映射)
- 监听路由(onhashchange)
- 处理路由变化,跟配置表的路径做匹配
- 路径转化为正则表达式
- 正则exec,匹配 抽取参数
其中难点就在于路径转化为正则表达式,director没做好就是这一步,而backbone则做得非常非常强大,那么我们可以尝试把backbone这一块代码抠出来。
路由表:
代码语言:javascript复制 var Route = root.Route = {
init: function (map) {
var defaultAction = map['*'];
if(defaultAction){
Route.defaultAction = defaultAction;
delete map['*'];
}
Route.routes = map;
init();
onchange();
},
routes: {},
defaultAction: null
};
监听路由变化:
代码语言:javascript复制 /**
* 这段判断,引用于director:https://github.com/flatiron/director
*/
function init(){
if ('onhashchange' in window && (document.documentMode === undefined
|| document.documentMode > 7)) {
// At least for now HTML5 history is available for 'modern' browsers only
if (this.history === true) {
// There is an old bug in Chrome that causes onpopstate to fire even
// upon initial page load. Since the handler is run manually in init(),
// this would cause Chrome to run it twise. Currently the only
// workaround seems to be to set the handler after the initial page load
// http://code.google.com/p/chromium/issues/detail?id=63040
setTimeout(function() {
window.onpopstate = onchange;
}, 500);
}
else {
window.onhashchange = onchange;
}
this.mode = 'modern';
} else {
throw new Error('sorry, your browser doesn't support route');
}
}
处理路由变化,先拼凑正则表达式:
代码语言:javascript复制 /**
* 引自backbone,非常牛逼的正则
* @param route
* @returns {RegExp}
*/
function getRegExp(route){
var optionalParam = /((.*?))/g;
var namedParam = /((?)?:w /g;
var splatParam = /*w /g;
var escapeRegExp = /[-{}[] ?.,\^$|#s]/g;
route = route.replace(escapeRegExp, '\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) {
return optional ? match : '([^/?] )';
})
.replace(splatParam, '([^?]*?)');
return new RegExp('^' route '(?:\?([\s\S]*))?$');
}
从原来的:module2/:name变成标准的正则表达式,个中奥妙大家自行顿悟
循环匹配:
代码语言:javascript复制 function onchange(onChangeEvent){
var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
var url = newURL.replace(/.*#/, '');
var found = false;
for (var path in Route.routes) {
var reg = getRegExp(path);
var result = reg.exec(url);
if(result && result[0] && result[0] != ''){
var handler = Route.routes[path];
handler && handler.apply(null, result.slice(1));
found = true;
}
}
if(!found && Route.defaultAction){
Route.defaultAction();
}
}
然后。。。做个简单的测试:
代码语言:javascript复制<script src="libs/backbone-route.js"></script>
<script>
Route.init({
'module1': function(){
console.log(1);
},
'module2/:name/:age': function(){
console.log(2, arguments);
},
'module3(/:name)(/:age)': function(){
console.log('3', arguments);
},
'*': function(){
console.log(404);
}
});
</script>
本文代码:https://github.com/kenkozheng/HTML5_research/tree/master/backbone-route