一步步实现一个简单的router插件

单页应用在随着框架的诞生已经是大趋势,自己平时在项目中也已经用了不少vur-router的代码了。用久了自然对其中的实现好奇了起来,于是想着自己能不能做出一个简单的vue-router。一开始扒了源码研究,实在是看不懂。于是从一篇篇文章开始慢慢补充起自己的基础。

一、写一个最简单的前端路由

如果使用过vue-router的人应该知道,Router插件是通过切换hash值来切换页面的。而浏览器则提供了一个hashchange的回调方法监听的hash值的变化。我们可以就这个原生API实现一个简单的前端路由。

之所以用这个API作为我们第一个出场嘉宾,其好处是可以兼容低版本的IE8浏览器

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
<a href="#/">home</a>
<a href="#/router1">router1</a>
<a href="#/router2">router2</a>
<div id="content"></div>
--------
class Router {
constructor(router = {}){
this.router = router;
this.init();
}
refresh(){
let url = window.location.hash.slice(1) || "/";
if(this.router[url]){
this.router[url]();
}else{
throw new Error("It isn't a router name that registered");
}
}
init(){
window.addEventListener("hashchange",this.refresh.bind(this),false);
}
}
function changeContent(text){
document.getElementById("content").innerText = text;
}
let router = new Router({
"/"(){
changeContent('home')
},
'/router1'(){
changeContent('router1')
},
'/router1'(){
changeContent('router2')
}
})

当浏览器监听到hash值发生改变之后,自动调用对应路由提供的回调,这样子就出现了一个简单的单页路由demo。

二、实现一个简单的H5版本的前端路由

随着HTML5的发布,我们则多了一个新的选择history

浏览器原生的history对象为我们提供了一些新的方法如下,具体意思基本上也可以做

1
2
3
4
5
history.go(); //加载 history 列表中的某个具体页面。
history.back(); //加载 history 列表中的前一个 URL,等同于history.go(-1)。
history.foward(); //加载 history 列表中的下一个 URL,等同于history.go(1)。
history.pushState(state,title,url); //添加一条页面地址记录
history.replaceState(state,title,url); //更新当前的历史地址记录

这里再额外说一句pushState方法和replaceState的区别,从网上找了一张图来表示

image

pushState()是在history栈中添加一个地址并同时跳往该地址,replaceState()是直接替换当前地址为新地址。

每当你使用pushState()或者replaceState()方法时,会触发popstate事件的回调

然后我们以这两个方法为基础,重新写一个Router的类

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
<a href="#/">home</a>
<a href="#/router1">router1</a>
<a href="#/router2">router2</a>
<div id="content"></div>
--------
class Router{
constructor(router = {}){
this.router = router;
this.init();
}
route(path,cb){
this.router[path] = cb || function () {
throw new Error("please give a callback");
};
}
refresh(state){
let url = state.path || "/";
if(this.router[url]){
this.router[url]();
}else{
throw new Error("It's not register router");
}
}
route(path,cb){
this.router[path] = cb || function () {
throw new Error("please give a callback");
};
}
init(){
let _this = this;
window.addEventListener("popstate",function (event) {
this.refresh(event.state || {});
}.bind(this))
document.querySelectorAll("a").forEach(a => {
a.addEventListener("click",function (e) {
e.preventDefault();
let path = link.slice(1) || "/";
let link = a.getAttribute("href");
_this.refresh({path : path} || {});
if(e.target.getAttribute("type") === 'replace'){
window.history.replaceState({'path':path},path,e.target.hre;
} else {
window.history.pushState({'path' : path},path,e.target.href);
}
})
},false);
//首次进入路由
let path = window.location.hash.slice(1) || '/';
this.refresh({path : path} || {});
}
}
function changeContent(text){
document.getElementById('content').innerHTML = text;
}
let router = new Router({
'/'(){
changeContent("home");
},
'/router1'(){
changeContent("router1");
},
'/router2'(){
changeContent("router2");
}
})
router.route('/router3',function(){
changeContent('路由3');
})

三、实现一个简单的vue-router版路由

因为vue-router是基于Vue的插件,所以很多可以发现源码很多地方,都拥有很典型的Vue特色。

第一步跟着文件中的index.js入手,大致可以找出几个初始函数来createMatcher,createRouterMap,