为什么要有 hash 和 history
对于 Vue 这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
为了达到这一目的,浏览器当前提供了以下两种支持:
- hash —— 即地址栏 URL 中的
#
符号(此 hash 不是密码学里的散列运算)。
比如这个 URL:[http://www.abc.com/#/hello](http://www.abc.com/#/hello)
,hash 的值为#/hello
。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。 - history —— 利用了 HTML5 History Interface 中新增的
pushState()
和replaceState()
方法。(需要特定浏览器支持)
这两个方法应用于浏览器的历史记录栈,在当前已有的back
、forward
、go
的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
因此可以说,hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。
使用场景
一般场景下,hash 和 history 都可以,除非你更在意颜值,#
符号夹杂在 URL 里看起来确实有些不太美丽。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。—— Vue-router 官网。
另外,根据 Mozilla Develop Network 的介绍,调用 history.pushState()
相比于直接修改 hash
,存在以下优势:
pushState()
设置的新 URL 可以是与当前 URL 同源的任意 URL;而hash
只可修改#
后面的部分,因此只能设置与当前 URL 同文档的 URL;pushState()
设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而hash
设置的新值必须与原来不一样才会触发动作将记录添加到栈中;pushState()
通过stateObject
参数可以添加任意类型的数据到记录中;而hash
只可添加短字符串;pushState()
可额外设置title
属性供后续使用。
当然啦,history
也不是样样都好。SPA 虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。
hash
模式下,仅hash
符号之前的内容会被包含在请求中,如[http://www.abc.com](http://www.abc.com)
,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。history
模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如[http://www.abc.com/book/id](http://www.abc.com/book/id)
。如果后端缺少对/book/id
的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”小结
结合自身例子,对于一般的 Vue + Vue-Router + Webpack + XXX 形式的 Web 开发场景,用history
模式即可,只需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。Hash History
平时单页面网站经常使用的模式,#又两种情况,一个是我们所谓的锚点,比如典型的回到顶部原理、Github上各个标题之间的跳转等,路由里面的#不叫锚点,我们称之为hash,大型框架的路由系统大多都是hash实现的。实现原理
改变#不触发网页重载
比如
浏览器不会重新向服务器请求index.htmlhttp://www.example.com/index.html#location1 // 改成 http://www.example.com/index.html#location
改变#会改变浏览器的访问历史
每一次改变#后面的部分,都会在浏览器的访问历史中增加一个记录,是用“后退”按钮,就可以回到上一个位置。
onhashchange事件去监测hash的改变
三种实现方式
具体实现方法 ```javascriptwindow.onhashchange = func; <body onhashchange="func();"> window.addEventListener("hashchange", func, false);
<a name="DDCU0"></a> ### 缺点 SEO<br />页面都变成了全JS生成,搜索引擎及三方统计无法进行抓起。<br />解决方案: - Google抓取AJAX方案 - 再做一个服务端生成内容的镜像网站 - HTML5 history中的PushState(另一种路由模式) <a name="pXoAy"></a> ## HTML5 history 这种路由模式下,没有难看的#号,url和后台路由看起来没什么差别 <a name="Ij5hn"></a> ### 实现原理 HTML5 新增的历史记录API可以实现无刷新更改地址栏链接,配合AJAX可以做到无刷新跳转。 ```javascript window.history.pushState(null, null, "/profile/");
popState事件
当用于点击浏览器的 “前进”“后退”按钮时,就会触发popState事件,你可以监听这一事件,从而作出反应。
window.addEventListener("popstate", function(e) { var state = e.state; // do something... });
这里e.state就是当初pushState时传入的第一个参数,state 对象可以是任何可以序列化的东西。由于 火狐 会将这些对象存储在用户的磁盘上,所以用户在重启浏览器之后这些state对象会恢复。参见 MDN pushState API
replaceState方法
有时,你不希望添加一个新纪录,而是替换当前的记录(比如对网站的landing page),则可以使用replaceState方法。这个方法和pushState的参数完全一样
具体实现代码
<button onclick="link('/a')">to /a</button> <button onclick="link('/b')">to /b</button> <script> function link(url) { console.log('link to: ' + url); window.history.pushState(null, null, url); } // 仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发 window.addEventListener('popstate', function(e) { alert("location: " + document.location + ", state: " + JSON.stringify(event.state)); }, false); </script>
缺点:
当使用HTML5 history,刷新页面时会出现404。是因为当前url服务端无法找到,所以,要在服务端增加一个覆盖所有情况的候选资源,如果url匹配不到任何资源,则应该返回同一个index.html页面,这个页面就是你app依赖的页面,同时需要要在前端这边处理404。