一个一百行内的现代的javascript路由
2015-06-23 16:23
791 查看
时下流行的单页的应用无处不在。有了这样的应用意味着你需要一个坚实的路由机制。像Emberjs框架是真正建立在一个路由器类的顶部。我真不知道,这是我喜欢的一个概念,但我绝对相信AbsurdJS应该有一个内置的路由器。而且,与一切都在这个小库,它应该是小的,简单的类。让我们来看看这样的模块可能长什么样。
在一百行以内。
支持hash类型的 URLs如: like http://site.com#products/list.
支持History API。
提供易用的API.
不自动运行。
只在需要的情况下监听变化。
这里有三个我们所需的特性。
routes:保存当前已注册的路由。
mode: 显示“hash”或者“history”取决于我们是否运用History API.
root: 应用的根路径,只在用pushState的情况下需要。
mode相当于“history”只有当我们要和当然只能是支持pushState。否则,我们将在URL中的用hash。默认情况下,root设置为单斜线“/”。
在这两种情况下,使用的是全局window.location的对象。在“history”的模式版本,需要删除URL的根部。还应该删除所有GET参数,这是用一个正则表达式(/\?(.*)$/)完成。获得hash的值更加容易。注意clearSlashes功能的使用。它的任务是去掉从开始和字符串的末尾删除斜杠。这是必要的,因为我们不希望强迫开发者使用的URL的特定格式。不管他通过它转换为相同的值。
该函数填充路由数组,如果只有一个函数传递,则它被认为是默认路由,这仅仅是一个空字符串的处理程序。请注意,大多数函数返回this。这将帮助我们的连锁类的方法。
删除只发生在通过一个传递匹配的正则表达式或传递handler参数给add方法。
有时,我们可能需要重新初始化类。所以上面的flush方法可以在这种情况下被使用。
通过使用getFragment方法或者接收它作为函数的参数来获得fragment。之后对路由进行一个正常的循环,并试图找到一个匹配。如果正则表达式不匹配,变量匹配该值为NULL。或者,它的值像下面
它的类数组对象包含所有的匹配字符串和子字符串。这意味着,如果我们转移的第一个元素,我们将得到的动态部分的数组。例如:
脚本输出:
这就是我们如何处理动态 URLs.
我们需要保持最新url,以便我们能够把它和最新的做对比。
同样,我们做法不同取决于我们的mode属性。如果History API可用我们可以用pushState,否则,用window.location就行了。
原文参考
要求
路由应该是:在一百行以内。
支持hash类型的 URLs如: like http://site.com#products/list.
支持History API。
提供易用的API.
不自动运行。
只在需要的情况下监听变化。
单列模式
创建一个路由实例可能是一个糟糕的选择,因为项目可能需要几个路由,但是这是不寻常的应用程序。如果实现了单列模式,我们将不需要从一个对象到另一个对象传递路由,不必担心创建它。我们希望只有一个实例,所以可能会自动创建它。var Router = { routes: [], mode: null, root: '/' }
这里有三个我们所需的特性。
routes:保存当前已注册的路由。
mode: 显示“hash”或者“history”取决于我们是否运用History API.
root: 应用的根路径,只在用pushState的情况下需要。
认证
我们需要一个路由器的方法。将该方法添加进去并传递两个参数。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设置为单斜线“/”。
获得当前URL
这是路由中的重要部分,因为它将告诉我们当前所处的位置。我们有两种模式,所以我们需要一个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的特定格式。不管他通过它转换为相同的值。
clearSlashes: function(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); }
添加和删除路由
在开发AbsurdJS时,我总是的给开发者尽可能多的控制。在几乎所有的路由器实现的路由被定义为字符串。不过,我更喜欢直接传递一个正则表达式。它更灵活,因为我们可能做的非常疯狂的匹配。add: function(re, handler) { if(typeof re == 'function') { handler = re; re = ''; } this.routes.push({ re: re, handler: handler}); return this; }
该函数填充路由数组,如果只有一个函数传递,则它被认为是默认路由,这仅仅是一个空字符串的处理程序。请注意,大多数函数返回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; }
删除只发生在通过一个传递匹配的正则表达式或传递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,以便我们能够把它和最新的做对比。
更改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,它真的是有用的如果你不想因为路由而引用一整个框架。原文参考
相关文章推荐
- Jsoup解析HTML实例(2)
- Jsoup解析HTML实例(1)
- 201506231015_《Javascript权威指南(第六版)——作为命名空间的函数、闭包、 》(P181-193)
- JavaScript之正则表达式
- js禁止网页使用右键
- javascript闭包
- js中的arguments对象
- D3.js download
- 【JavaScript】程序入门基础(一)
- [Code] 《JS权威指南》示例程序 -- Loan Calculator
- KnockoutJS
- 分享的js代码,从w3c上拓下来的
- json中key大小写转换
- js新增ul
- JS动态标签创建
- JavaScript学习笔记(对象)
- bzoj1013 [JSOI2008]球形空间产生器sphere
- SeaJS入门
- js编写中遇到的问题-TypeError: document.body is null
- js遍历数组的错误方法