你們用backbone、angular,可能都習慣了內置的路由,這兩個框架的路由都是很是優秀的,強大而簡單。git
客戶端(瀏覽器)路由原理其實比較簡單,其實就是監聽hash的變化。github
在以前的架構探討中,說到director.js這個路由類庫很差使,那麼,在這一篇,咱們嘗試自行實現一個簡潔並且很是好使的路由類庫。正則表達式
原理先介紹,無非幾個步驟:瀏覽器
其中難點就在於路徑轉化爲正則表達式,director沒作好就是這一步,而backbone則作得很是很是強大,那麼咱們能夠嘗試把backbone這一塊代碼摳出來。架構
路由表:app
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變成標準的正則表達式,箇中奧妙你們自行頓悟測試
循環匹配:this
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