路由的学习与探究

什么是路由

路由(route)是什么?我们抛开生活中的路由不谈,在 web 开发中,路由的主要工作是根据 url 分配对应的处理程序。

假如有一个服务,域名为 a.com,它提供了一个可供访问的页面 http://a.com/about,当用户输入 http://a.com/about 访问时,Web 服务会根据用户输入的这个 URL 找到对应的处理程序,这个过程就是路由分发。

前端路由

在以前,我们一般是在后端做路由(想想熟悉的 Spring MVC 的控制器),分析处理 URL。但是随着前端的发展,单页面应用的流行,页面交互越来越复杂,一些路由的处理已经交给了前端。如今很多流行的 js 类库框架:AngularJS、Backbone、Vue、React 等等都支持前端路由,那么前端路由的原理是什么呢?

在 HTML5 出现以前,通常使用 hash 来实现路由功能。那么 hash 是什么呢?比如 www.nekolr.com/#git,这个 # 号及后面的字符被称为 hash。当 hash 值改变时,浏览器会产生历史记录,但不会向服务器发送请求,此时后退地址栏会发生变化但页面不会刷新。在了解了 hash 之后,还需要了解 hashchange 事件,该事件在 hash 值改变时触发,这里有个小栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hashchange</title>
</head>
<body>
<ul>
<li><a href="#first">first</a></li>
<li><a href="#second">second</a></li>
</ul>
</body>
<script type="text/javascript">
window.addEventListener("hashchange", function (e) {
console.log(window.location.hash)
console.log("这是之前的:" + e.oldURL)
console.log("这是新的:" + e.newURL)
})
</script>
</html>

手写一个前端路由

下面结合这两个知识点,我们来做一个简单的前端路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>blog</title>
<style>
nav{line-height:2em;}
nav a{display:inline-block;vertical-align:top;padding:0 .5em;}
nav a.active{background:#369;color:#FFF;}
</style>
</head>
<body>
<nav>
<a href="#!home" class="active"> 首页 </a>
<a href="#!about"> 关于 </a>
<a href="#!friend"> 友情链接 </a>
</nav>
<div id="content"></div>
</body>
<script type="text/javascript">

function Router() {
this.key = "!";
this.currentUrl = "";
this.routes = {};//路由表
}

/**
* 注册路由
* @param address
* @param callback
*/
Router.prototype.reg = function (address, callback) {
this.routes[this.key+address] = callback;
};


/**
* 跳转到
*/
Router.prototype.go = function () {
this.currentUrl = window.location.hash.slice(1) || "!home";
this.routes[this.currentUrl]();
};

/**
* 初始化
* @param callback 一个回调函数,用来处理通用的操作
*/
Router.prototype.init = function (callback) {
var self = this;

window.addEventListener("hashchange", function () {
self.go.bind(self)()
if (typeof callback === "function")
callback.bind(self)(self.currentUrl)
}, false);
// 页面初始化加载首页
window.addEventListener("load", this.go.bind(this), false);
}

/**********************使 用************************/

var router = new Router();

router.init(function (currentUrl) {
var a = document.querySelector('nav a.active')
var i = document.querySelector('nav a[href="#'+currentUrl+'"]')
if(a) // 如果存在这个 DOM
a.className = '';
if(i)
i.className = 'active';
});

router.reg("home", function () {
document.querySelector("#content").innerHTML = "这是首页";
// 实际使用中,在这里调用 ajax 来实现无刷新更新页面内容
})

router.reg("about", function () {
document.querySelector("#content").innerHTML = "这是关于页";
// 实际使用中,在这里调用 ajax 来实现无刷新更新页面内容
})

router.reg("friend", function () {
document.querySelector("#content").innerHTML = "这是友链页";
// 实际使用中,在这里调用 ajax 来实现无刷新更新页面内容
})

</script>
</html>

基本的思路就是:将关键字和对应的处理函数提前注册到路由表中,在页面 hash 改变,触发 hashchange 事件时调用对应的处理函数。

前端路由一般在单页面应用(SPA)使用较多,优点主要是用户体验好,页面内容不需要每次都从服务器全部获取,能够快速展现给用户。缺点包括使用浏览器的前进、后退键会重新发送请求,没有合理地利用缓存,无法记住之前滚动的位置等。

参考

Web 开发中的”路由”是什么意思?