SPA单页面应用通过路由的方式实现页面内容的更改,主要用到了Hash和H5 History技术。

Hash模式

原理:当URL的Hash值改变时,触发hashchange事件,执行相应操作。

有三个问题:

  1. 什么是Hash值
  2. 用什么改变Hash值
  3. 执行什么操作

1)Hash就是#后面的内容,改变hash不会向服务器发起请求。比如这样一条URL:

http://https://ymlog.cn/2020/12/14/JS%207-Promise%E8%AF%AD%E6%B3%95/#PromiseA

其结构为:

1
2
3
4
5
6
7
{
location.href : 'https://ymlog.cn/2020/12/14/JS%207-Promise%E8%AF%AD%E6%B3%95/#PromiseA',
hash: '#PromiseA',
pathname: '/2020/12/14/JS%207-Promise%E8%AF%AD%E6%B3%95/',
origin: 'https://ymlog.cn',
protocal: 'https:'
}

2)我们可以用a标签的href改变hash值,比如,点击回车后,原本的URL就会变成http://xxx.com/index.html#/page2

1
<a href="#/page2">page2</a>

3)当hashchange事件改变后,我们需要执行的操作就是查询hash,看是否有对应的callback回调函数,如果有,则执行回调函数

下面我们用代码来实现一下:

定义HTML结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hash router</title>
</head>
<body>
<ul>
<li><a href="#/">/</a></li>
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
</ul>
<div class='content-div'></div>
<script src="./index.js"></script>
</body>
</html>

JS处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 添加路由和路由回调函数
// 2. 注册hashchange方法,当路由改变时,执行路由的回调函数
// 一点优化:当页面初次加载的时候,用load事件加载路由

// 添加路由和回调
var routers = {};
const changePage = v => document.querySelector('.content-div').innerHTML = v;
routers['/page1'] = _ => changePage("这是page1");
routers['/page2'] = _ => changePage("这是page2");
routers['/'] = _ => changePage("这是首页");

// 注册hashchange方法
window.addEventListener('hashchange', function () {
let hash = location.hash.slice(1); // 除去#
routers[hash] && routers[hash](); // 存在路由则执行路由方法
})

// 优化项
window.addEventListener('load', () => {
let ch = location.hash.slice(1);
ch = ch !== '' ? routers[ch]() : routers['/']();
})

hashrouterhistory

History模式

先介绍一下H5 History的API:

  1. history.go(n):路由跳转,比如n为 2 是往前移动2个页面,n为 -2 是向后移动2个页面,n为0是刷新页面
  2. history.back():路由后退,相当于 history.go(-1)
  3. history.forward():路由前进,相当于 history.go(1)
  4. history.pushState():添加一条路由历史记录,如果设置跨域网址则报错
  5. history.replaceState():替换当前页在路由历史记录的信息
  6. popstate 事件:当活动的历史记录发生变化,就会触发 popstate 事件,在点击浏览器的前进后退按钮或者调用上面前三个方法的时候也会触发

采用History模式设置动态路由,需要做以下几步:

  1. 添加路由和路由回调方法
  2. 阻止<a>的冒泡,添加a的点击事件
  3. 添加Go方法,压入路由 执行路由回调函数
  4. 添加popstate,在浏览器前进和后退的时候执行相应路由方法

HTML,和上面类似,但href中的#不需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hash router</title>
</head>
<body>
<ul>
<li><a href="/">/</a></li>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div class='content-div'></div>
<script src="./index.js"></script>
</body>
</html>

JS

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
// 1 添加路由和回调 和上面类似
var routers = {};
const changePage = v => document.querySelector('.content-div').innerHTML = v;
routers['/page1'] = _ => changePage("这是page1");
routers['/page2'] = _ => changePage("这是page2");
routers['/'] = _ => changePage("这是首页");

document.querySelector('ul').addEventListener('click', e => {
let target = e.target;
// 2 阻止默认事件、获取<a>中的连接、执行自定义跳转方法
if (target.tagName === "A") {
e.preventDefault();
let href = target.getAttribute('href');
go(href);
}
})

function go(href) {
// 3 压入路由 执行路由回调函数
history.pushState({ href }, null, href);
routers[href] && routers[href]();
}

window.addEventListener('popstate', e => {
// 添加popstate,在浏览器前进和后退的时候执行相应路由方法
let pathname = location.pathname;
routers[pathname] && routers[pathname]();
})

// 加载优化
window.addEventListener('load', e => {
let pathname = location.pathname;
routers[pathname] && routers[pathname]();
})

historyrouter

References

https://segmentfault.com/a/1190000018081475

https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState

https://developer.mozilla.org/zh-CN/docs/Web/API/History_API