路由
后端路由
多页应用中,一个URL对应一个HTML页面,一个Web应用包含很多HTML页面,在多页应用中,页面路由控制由服务器端负责,每次页面切换都需要向服务器发送一次请求,页面使用到的静态资源也需要重新加载,存在一定的浪费
前端路由
在web前端单页应用SPA中,路由描述的是URL与UI之间的映射关系,这种映射是单向的,即URL变化引起UI更新(无需刷新页面)
如何实现前端路由
改变URL,不引起页面刷新
- hash实现:hash是URL中#及后面的部分,常用作锚点在页面内进行导航,改变URL中hash部分不会引起页面刷新
- history实现:history提供了pushState和replaceState中两个方法,这两个方法改变URL的path部分不会引起页面刷新
检测URL变化
- hash实现:通过hashChange事件监听URL的变化,以下改变URL的方式会触发hashChange:
- 通过浏览器的前进后退
- 通过a标签
- 通过window.location
- history实现:history提供类似hashChange的popstate事件,通过以下几种方式监听URL变化
- 浏览器前进后退
- 拦截pushState/replaceState
- 拦截a标签的点击事件
图解
举例
hash demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/">首页</a></li>
<li><a href="#/list">列表页面</a></li>
<li><a href="#/cart">购物车页面</a></li>
<li><a href="#/home">我的</a></li>
</ul>
<div id="content"></div>
<script src="./js/hashRouter.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// hashRouter.js
class Router {
constructor(routes) {
this.routes = {}
this.init(routes)
this.onPopState()
}
init(routes) {
let _this = this
Object.keys(routes).forEach((item, index, array) => {
_this.listen(item, routes[item])
})
window.addEventListener('hashchange', this.refresh.bind(this,routes))
}
listen(url, fn) {
this.routes[url] = fn
}
refresh(routes) {
this.curUrl = location.hash.slice(1) || '/'
this.routes[this.curUrl] && this.routes[this.curUrl]()
}
onPopState() {
let _this = this
window.addEventListener('popstate', e => {
let url = e.state && e.state.url
_this.routes[url] && _this.routes[url]()
})
}
}
const route = new Router({
'/': () => render('首页内容'),
'/list': () => render('列表页内容'),
'/cart': () => render('购物车内容'),
'/home': () => render('我的内容')
})
function render (content) {
document.getElementById('content').innerHTML = content
}
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
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
history demo
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>路由实现</title>
</head>
<body>
<div class="nav" id="nav">
<a href="javascript:void(null)" onclick="route.go('/')">首页</a>
<a href="javascript:void(null)" onclick="route.go('/list')">列表页面</a>
<a href="javascript:void(null)" onclick="route.go('/cart')">购物车页面</a>
<a href="javascript:void(null)" onclick="route.go('/home')">我的</a>
</div>
<div id="content"></div>
<script src="./js/router.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// router.js
class Router {
constructor(routes) {
this.routes = {}
this.init(routes)
this.onPopState()
}
init(routes) {
let _this = this
Object.keys(routes).forEach((item, index, array) => {
_this.listen(item, routes[item])
})
this.go('/')
}
listen(url, fn) {
this.routes[url] = fn
}
go(url) {
history.pushState({url}, null, url)
this.routes[url] && this.routes[url]()
}
onPopState() {
let _this = this
window.addEventListener('popstate', e => {
let url = e.state && e.state.url
_this.routes[url] && _this.routes[url]()
})
}
}
const route = new Router({
'/': () => render('首页内容'),
'/list': () => render('列表页内容'),
'/cart': () => render('购物车内容'),
'/home': () => render('我的内容')
})
function render (content) {
document.getElementById('content').innerHTML = content
}
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
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