你也许会在很多网站上看到他们的链接都是http://example.com/#/xxx的形式,而且加载速度非常快。如果你是React/vue/ng的开发者,那你应该也对这个东西非常熟悉。今天就分析一下hash路由的原理。

基本原理

hash路由是一个hashtag升级实现形式,以前通常使用hashbang实现也就是#!这个标志,为的是不与jquery冲突,而现在随着MVVM框架的普及,越来越多的hash路由系统选择了#。而在url中#像分隔符一样,#及它以后的东西并不会改变请求url。所以这就给我们留下了动手空间。

好了,一个最简单的hash系统需要有些东西呢?

  1. 路由表
  2. 一个刷新器
  3. 回调机制

让我们回到万恶之源location.hash,这是获取与改变location.hash的最基本的方式,当然你还可以通过改变url的方式改变hash,诸如location.href ,<a href="#/xx">xx</a>等方式。

location.hash中还有一个重要的东西叫做window.onhashchange,这个东西可以在hash值改变时触发相关函数。

代码实现

好了,接下来可以开始实现了,这里使用ES6的class类来实现,当然 使用ES5的proptype也是可以的,

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
class Router {
//建立路由表
constructor() {
this.routes = {};
this.hashUrl = '';
}
/**
* 调用路由下回调函数
*/
route(path, callback) {
this.routes[path] = callback || function() {};
}
/**
* 状态刷新机制
*/
updateView() {
this.hashUrl = location.hash.slice(1) || '/';
this.routes[this.hashUrl] && this.routes[this.hashUrl]();
}
/**
* 初始化路由,在路由改变时启动状态刷新机制
*/
init() {
window.addEventListener('load', this.updateView.bind(this), false);
window.addEventListener('hashchange', this.updateView.bind(this), false);
}
}

当然,根据自身需求可以丰富这个最基本的路由,比如博主会在hash路由里新建search query。
所以在hashUrl里对location.hash?及其后面的字符进行了截取。而获取search query是写在了外部函数中,未来也要弄进class里来。

路由的功能有多大,完全取决于你的想象力,
你甚至可以根据页面文字的hash值(注意不是url的hash值)实现一个类似React-Router 2中?_k=xxxxxx的历史标签

之前说过,用ES5的prototype也是可以的
(PS: 当然博主选择了比较粗暴的方式=> babel,根据大家自己的喜好选择顺手的方式就好啦)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Router() {
this.routes = {};
this.hashUrl = '';
}

Router.prototype = {

route: function (path, callback) {
this.routes[path] = callback || function(){};
},

updateView: function () {
this.hashUrl = location.hash.slice(1) || '/';
this.routes[this.hashUrl] && this.routes[this.hashUrl]();
},

init: function () {
window.addEventListener('load', this.updateView.bind(this), false);
window.addEventListener('hashchange', this.updateView.bind(this), false);
}
}

好了,基本原理弄清楚后,就可以开始编写路由了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

var router = new Router();
router.init();

router.route('/', function () {
if (location.hash.length == 0) {
location.hash = "/";
}
document.getElementById('content').innerHTML = 'Home';
});

router.route('/about', function () {
document.getElementById('content').innerHTML = 'About';
});

router.route('/links', function () {
document.getElementById('content').innerHTML = 'Links';
});

为什么使用hash路由

hash路由有很多优点

  1. 快,代码是在网页载入时就已经下载,只有切换时才调用,如果你每个router都要发起大量的ajax或者fetch请求的话,让你的服务器运维把http2的connect时间开大点,用户体验极好
  2. 兼容性很好

当然缺点也有

  1. 爬虫体验极差,不是说不能爬,你的爬虫得学会理解并执行js。谷歌bot在爬取hash路由的网页效果也很差,这也是博主为何没有启用自己编写的博客的原因
  2. 与jq混用需谨慎,坑非常多,你可能需要重构大量代码,最好还是与Vanilla.js搭配食用(笑)
  3. 过渡动画不是很好写,感觉有些生硬

history路由与hash路由

history路由是根据h5的新api弄出来的东西,使用的是history.pushStatehistory.replaceState来改变状态的,同样history可以用来操纵hash路由,但是通常没人这么做。一般情况下,它直接改变的是pathname,通过对后端的设置来进行rewrite防止404错误,而前端对history及pathname的读取来刷新状态,这样做虽然使url整洁爬虫友好,但是rewrite设置非常麻烦,你的api以及static静态文件要单独设置,很麻烦,这也是我讨厌history路由的原因