vue hash 路由和 history 路由的区别

hash 模式

hash 模式是一种把前端路由的路径用井号 # 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件。

示例:新建一个 hash.html 文件,内容为:

1
2
3
4
5
6
7
8
9
10
11
<a href="#/a">A 页面</a>
<a href="#/b">B 页面</a>

<div id="app"></div>
<script>
function render() {
app.innerHTML = window.location.hash;
}
window.addEventListener("hashchange", render);
render();
</script>

在上面的例子中,我们利用 a 标签设置了两个路由导航,把 app 当做视图渲染容器,当切换路由的时候触发视图容器的更新,这其实就是大多数前端框架哈希路由的实现原理。

总结一下 hash 模式的优缺点:

  • 优点:浏览器兼容性较好,连 IE8 都支持
  • 缺点:路径在井号 # 的后面,比较丑

history 模式

history API 是 H5 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求。

示例:新建一个 history.html,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<a href="javascript:toA();">A 页面</a>
<a href="javascript:toB();">B 页面</a>

<div id="app"></div>

<script>
function render() {
app.innerHTML = window.location.pathname;
}
function toA() {
history.pushState({}, null, "/a");
render();
}
function toB() {
history.pushState({}, null, "/b");
render();
}
window.addEventListener("popstate", render);
</script>

history API 提供了丰富的函数供开发者调用,我们不妨把控制台打开,然后输入下面的语句来观察浏览器地址栏的变化:

1
2
3
4
5
history.replaceState({}, null, "/b"); // 替换路由
history.pushState({}, null, "/a"); // 路由压栈
history.back(); // 返回
history.forward(); // 前进
history.go(-2); // 后退 2 次

上面的代码监听了 popstate 事件,该事件能监听到:

  • 用户点击浏览器的前进和后退操作
  • 手动调用 historybackforwardgo 方法

监听不到:

  • history 的 pushStatereplaceState 方法

这也是为什么上面的 toA 和 toB 函数内部需要手动调用 render 方法的原因。另外,大家可能也注意到 light-server 的命令多了 --historyindex '/history.html' 参数,这是干什么的呢?

浏览器在刷新的时候,会按照路径发送真实的资源请求,如果这个路径是前端通过 history API 设置的 URL,那么在服务端往往不存在这个资源,于是就返回 404 了。上面的参数的意思就是如果后端资源不存在就返回 history.html 的内容。

因此在线上部署基于 history API 的单页面应用的时候,一定要后端配合支持才行,否则会出现大量的 404。以最常用的 Nginx 为例,只需要在配置的 location / 中增加下面一行即可:

try_files $uri /index.html;

总结一下 history 模式的优缺点:

  • 优点:路径比较正规,没有井号 #
  • 缺点:兼容性不如 hash,且需要服务端支持,否则一刷新页面就 404 了

对比

  1. hash 路由在地址栏 URL 上有#,用 window.location.hash 读取。而 history 路由没有会好看一点

  2. 我们进行回车刷新操作,hash 路由会加载到地址栏对应的页面,而 history 路由一般就 404 报错了(刷新是网络请求,没有后端准备时会报错)。

  3. hash 路由支持低版本的浏览器,而 history 路由是 HTML5 新增的 API。

  4. hash 的特点在于它虽然出现在了 URL 中,但是不包括在 http 请求中,所以对于后端是没有一点影响的,所以改变 hash 不会重新加载页面,所以这也是单页面应用的必备。

  5. history 运用了浏览器的历史记录栈,之前有 back,forward,go 方法,之后在 HTML5 中新增了 pushState()和 replaceState()方法,它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的 URL,但是浏览器不会马上向后端发送请求。

  6. history 的这种模式需要后台配置支持。比如:当我们进行项目的主页的时候,一切正常,可以访问,但是当我们刷新页面或者直接访问路径的时候就会返回 404,那是因为在 history 模式下,只是动态的通过 js 操作 window.history 来改变浏览器地址栏里的路径,并没有发起 http 请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起 http 请求,但是这个目标在服务器上又不存在,所以会返回 404

总结

当然 history 也不是样样都好。SPA 虽然在浏览器里游刃有余,单真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。

  1. hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
  2. history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致。如 htttp://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。