前端路由 hash + history
前言
前端路由是现代SPA应用必备的功能,每个现代前端框架都有对应的实现,例如vue-router、react-router。
我们不想探究vue-router或者react-router们的实现,因为不管是哪种路由无外乎用兼容性更好的hash实现或者是H5 History实现,与框架几个只需要做相应的封装即可。
hash路由
hash路由一个明显的标志是带有#,我们主要是通过监听url中的hash变化来进行路由跳转。
- 优势:兼容性更好,在老版IE中都有运行
- 劣势:url中一直存在
#
不够美观,而且hash路由更像是Hack而非标准
class Routers {
constructor() {
// 储存hash与callback键值对
this.routes = {};
// 当前hash
this.currentUrl = '';
// 记录出现过的hash
this.history = [];
// 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
this.currentIndex = this.history.length - 1;
this.refresh = this.refresh.bind(this);
this.backOff = this.backOff.bind(this);
// 默认不是后退操作
this.isBack = false;
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, callback) {
this.routes[path] = callback || function() {};
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/';
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1)
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routes[this.currentUrl]();
console.log('指针:', this.currentIndex, 'history:', this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
this.currentIndex <= 0
? (this.currentIndex = 0)
: (this.currentIndex = this.currentIndex - 1);
location.hash = `#${this.history[this.currentIndex]}`;
this.routes[this.history[this.currentIndex]]();
}
}
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
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
HTML5新路由方案 History API
class Routers {
constructor() {
this.routes = {};
// 在初始化时监听popstate事件
this._bindPopState();
}
// 初始化路由
init(path) {
history.replaceState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 将路径和对应回调函数加入hashMap储存
route(path, callback) {
this.routes[path] = callback || function() {};
}
// 触发路由对应回调
go(path) {
history.pushState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 监听popstate事件
_bindPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}
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
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