您的位置:首页 > Web前端 > JavaScript

一个一百行内的现代的javascript路由

2015-06-23 16:23 791 查看
时下流行的单页的应用无处不在。有了这样的应用意味着你需要一个坚实的路由机制。像Emberjs框架是真正建立在一个路由器类的顶部。我真不知道,这是我喜欢的一个概念,但我绝对相信AbsurdJS应该有一个内置的路由器。而且,与一切都在这个小库,它应该是小的,简单的类。让我们来看看这样的模块可能长什么样。

要求

路由应该是:

在一百行以内。

支持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,它真的是有用的如果你不想因为路由而引用一整个框架。

原文参考
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: