大家用backbone、angular,可能都习惯了内置的路由,这两个框架的路由都是非常优秀的,强大而简单。
客户端(浏览器)路由原理其实比较简单,其实就是监听hash的变化。
在之前的架构探讨中,说到director.js这个路由类库不好使,那么,在这一篇,我们尝试自行实现一个简洁而且非常好使的路由类库。
原理先介绍,无非几个步骤:
其中难点就在于路径转化为正则表达式,director没做好就是这一步,而backbone则做得非常非常强大,那么我们可以尝试把backbone这一块代码抠出来。
路由表:
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 };
监听路由变化:
/** * 这段判断,引用于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'); } }
处理路由变化,先拼凑正则表达式:
/** * 引自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变成标准的正则表达式,个中奥妙大家自行顿悟
循环匹配:
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(); } }
然后。。。做个简单的测试:
<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