时下流行的单页的应用无处不在。有了这样的应用意味着你须要一个坚实的路由机制。像Emberjs框架是真正创建在一个路由器类的顶部。我真不知道,这是我喜欢的一个概念,但我绝对相信AbsurdJS应该有一个内置的路由器。并且,与一切都在这个小库,它应该是小的,简单的类。让咱们来看看这样的模块可能长什么样。正则表达式
路由应该是:api
建立一个路由实例多是一个糟糕的选择,由于项目可能须要几个路由,可是这是不寻常的应用程序。若是实现了单列模式,咱们将不须要从一个对象到另外一个对象传递路由,没必要担忧建立它。咱们但愿只有一个实例,因此可能会自动建立它。数组
var Router = { routes: [], mode: null, root: '/' }
这里有三个咱们所需的特性。浏览器
咱们须要一个路由器的方法。将该方法添加进去并传递两个参数。app
var Router = { routes: [], mode: null, root: '/', config: function(options) { this.mode = options && options.mode && options.mode == 'history' && !!(history.pushState) ? 'history' : 'hash'; this.root = options && options.root ? '/' + this.clearSlashes(options.root) + '/' : '/'; return this; } }
mode至关于“history”只有当咱们要和固然只能是支持pushState。不然,咱们将在URL中的用hash。默认状况下,root设置为单斜线“/”。框架
这是路由中的重要部分,由于它将告诉咱们当前所处的位置。咱们有两种模式,因此咱们须要一个if语句。函数
getFragment: function() { var fragment = ''; if(this.mode === 'history') { fragment = this.clearSlashes(decodeURI(location.pathname + location.search)); fragment = fragment.replace(/\?(.*)$/, ''); fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment; } else { var match = window.location.href.match(/#(.*)$/); fragment = match ? match[1] : ''; } return this.clearSlashes(fragment); }
在这两种状况下,使用的是全局window.location的对象。在“history”的模式版本,须要删除URL的根部。还应该删除全部GET参数,这是用一个正则表达式(/\?(.*)$/)完成。得到hash的值更加容易。注意clearSlashes功能的使用。它的任务是去掉从开始和字符串的末尾删除斜杠。这是必要的,由于咱们不但愿强迫开发者使用的URL的特定格式。无论他经过它转换为相同的值。this
clearSlashes: function(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); }
在开发AbsurdJS时,我老是的给开发者尽量多的控制。在几乎全部的路由器实现的路由被定义为字符串。不过,我更喜欢直接传递一个正则表达式。它更灵活,由于咱们可能作的很是疯狂的匹配。url
add: function(re, handler) { if(typeof re == 'function') { handler = re; re = ''; } this.routes.push({ re: re, handler: handler}); return this; }
该函数填充路由数组,若是只有一个函数传递,则它被认为是默认路由,这仅仅是一个空字符串的处理程序。请注意,大多数函数返回this。这将帮助咱们的连锁类的方法。code
remove: function(param) { for(var i=0, r; i<this.routes.length, r = this.routes[i]; i++) { if(r.handler === param || r.re.toString() === param.toString()) { this.routes.splice(i, 1); return this; } } return this; }
删除只发生在经过一个传递匹配的正则表达式或传递handler参数给add方法。
flush: function() { this.routes = []; this.mode = null; this.root = '/'; return this; }
有时,咱们可能须要从新初始化类。因此上面的flush方法能够在这种状况下被使用。
好吧,咱们有添加和删除URLs的API。咱们也可以获得当前的地址。所以,下一个合乎逻辑的步骤是比较注册入口。
check: function(f) { var fragment = f || this.getFragment(); for(var i=0; i<this.routes.length; i++) { var match = fragment.match(this.routes[i].re); if(match) { match.shift(); this.routes[i].handler.apply({}, match); return this; } } return this; }
经过使用getFragment方法或者接收它做为函数的参数来得到fragment。以后对路由进行一个正常的循环,并试图找到一个匹配。若是正则表达式不匹配,变量匹配该值为NULL。或者,它的值像下面
["products/12/edit/22", "12", "22", index: 1, input: "/products/12/edit/22"]
它的类数组对象包含全部的匹配字符串和子字符串。这意味着,若是咱们转移的第一个元素,咱们将获得的动态部分的数组。例如:
Router .add(/about/, function() { console.log('about'); }) .add(/products\/(.*)\/edit\/(.*)/, function() { console.log('products', arguments); }) .add(function() { console.log('default'); }) .check('/products/12/edit/22');
脚本输出:
products ["12", "22"]
这就是咱们如何处理动态 URLs.
固然,不能一直运行check方法。咱们须要一个逻辑,它会通知地址栏的变化。当发上改变,即便是点击后退按钮, URL改变将触发popstate 事件。不过,我发现一些浏览器调度此事件在页面加载。这与其余一些分歧让我想到了另外一种解决方案。由于我想有监控,即便模式设为hash.我决定使用的setInterval.
listen: function() { var self = this; var current = self.getFragment(); var fn = function() { if(current !== self.getFragment()) { current = self.getFragment(); self.check(current); } } clearInterval(this.interval); this.interval = setInterval(fn, 50); return this; }
咱们须要保持最新url,以便咱们可以把它和最新的作对比。
在路由的最后须要一个函数,它改变了当前地址和触发路由的处理程序。
navigate: function(path) { path = path ? path : ''; if(this.mode === 'history') { history.pushState(null, null, this.root + this.clearSlashes(path)); } else { window.location.href.match(/#(.*)$/); window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path; } return this; }
一样,咱们作法不一样取决于咱们的mode属性。若是History API可用咱们能够用pushState,不然,用window.location就好了。
这个小例程是最终版本。
var Router = { routes: [], mode: null, root: '/', config: function(options) { this.mode = options && options.mode && options.mode == 'history' && !!(history.pushState) ? 'history' : 'hash'; this.root = options && options.root ? '/' + this.clearSlashes(options.root) + '/' : '/'; return this; }, getFragment: function() { var fragment = ''; if(this.mode === 'history') { fragment = this.clearSlashes(decodeURI(location.pathname + location.search)); fragment = fragment.replace(/\?(.*)$/, ''); fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment; } else { var match = window.location.href.match(/#(.*)$/); fragment = match ? match[1] : ''; } return this.clearSlashes(fragment); }, clearSlashes: function(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); }, add: function(re, handler) { if(typeof re == 'function') { handler = re; re = ''; } this.routes.push({ re: re, handler: handler}); return this; }, remove: function(param) { for(var i=0, r; i<this.routes.length, r = this.routes[i]; i++) { if(r.handler === param || r.re.toString() === param.toString()) { this.routes.splice(i, 1); return this; } } return this; }, flush: function() { this.routes = []; this.mode = null; this.root = '/'; return this; }, check: function(f) { var fragment = f || this.getFragment(); for(var i=0; i<this.routes.length; i++) { var match = fragment.match(this.routes[i].re); if(match) { match.shift(); this.routes[i].handler.apply({}, match); return this; } } return this; }, listen: function() { var self = this; var current = self.getFragment(); var fn = function() { if(current !== self.getFragment()) { current = self.getFragment(); self.check(current); } } clearInterval(this.interval); this.interval = setInterval(fn, 50); return this; }, navigate: function(path) { path = path ? path : ''; if(this.mode === 'history') { history.pushState(null, null, this.root + this.clearSlashes(path)); } else { window.location.href.match(/#(.*)$/); window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path; } return this; } } // configuration Router.config({ mode: 'history'}); // returning the user to the initial state Router.navigate(); // adding routes Router .add(/about/, function() { console.log('about'); }) .add(/products\/(.*)\/edit\/(.*)/, function() { console.log('products', arguments); }) .add(function() { console.log('default'); }) .check('/products/12/edit/22').listen(); // forwarding Router.navigate('/about');
这个路由仅90行左右,它支持hash类型的URLs和一个新的History API,它真的是有用的若是你不想由于路由而引用一整个框架。
原文参考:http://krasimirtsonev.com/blog/article/A-modern-JavaScript-router-in-1...