I'm Sun

如何写一个无刷新的网站(二)路由器的实现

实现一个路由器首先要确定路由的语法,最常见的语法是这样的(假设 host 是 www.imsun.net):

/:将匹配 www.imsun.net

/example:将匹配 www.imsun.net/example

/example/:param:将匹配 www.imsun.net/example/some_textsome_text 作为 param 的值返回

/example/:param1/:param2:将匹配 www.imsun.net/example/some_text/other_textsome_text 作为 param1 的值返回,other_text 作为 param2 的值返回

/example/p:param:将匹配 www.imsun.net/example/p001001 作为 param 的值返回

基本语法大体就是这些,使用斜杠进行分隔,使用冒号表示参数。

为了正确地匹配这些语法,我选择了使用正则表达式,将路由语法转化成新的正则表达式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var getPattern = function(route) {
var escapeRegExp, namedParam, optionalParam, pattern, splatParam;
optionalParam = /\((.*?)\)/g;
namedParam = /(\(\?)?:\w+/g;
splatParam = /\*\w+/g;
escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
pattern = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) {
if (optional) {
return match;
}
return '([^\/]+)';
})
.replace(splatParam, '(.*?)');
return '^' + pattern + '$';
};

举个例子:

1
2
3
4
5
6
7
8
9
// 假设当前 URL 是 'www.imsun.net/example/posts/p111'
var route = '/example/:catorgy/p:count';
var pattern = new RegExp(getPattern(route));
// pattern = /^/example/([^/]+)/p([^/]+)$/
console.log(location.pathname.match(pattern));
// ["/example/posts/p111", "posts", "111"]

这样我们就根据路由返回了匹配信息和参数。

然后我们还要实现添加路由的功能,每条路由记录包括了路由规则和匹配时调用的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Router = {};
// 路由规则表
var rules = this.rules = {};
// 添加路由
Router.route = function(route, callback) {
var pattern;
pattern = getPattern(route);
if (!rules[pattern]) {
rules[pattern] = [];
}
return rules[pattern].push({
fn: callback,
event: event || 'change'
});
};

然后是解析 URL 的函数,发生匹配时就调用对应的函数,并将匹配结果将作为参数传入。

1
2
3
4
5
6
7
8
9
10
Router.checkURL = function() {
var regexp = new RegExp();
for (var pattern in rules) {
regexp.compile(pattern);
args = location.pathname.match(regexp);
if (args)
for(var i = 0; i < rules[pattern].length; i++)
rules[pattern][i].fn.apply(this, args.slice(1));
}
}

这样路由部分就基本写完了。个人有一个开源项目 PRouter.js,打包实现了路由器和下一篇要讲的无刷新操作 URL 的功能。有能力的也可以直接去读 Backbone 源码的 router 部分。