HashHistory

hash(“#”)符号的本来作用是加在URL中指示网页中的位置,#符号本身以及它后面的字符称之为hash,可通过window.location.hash属性读取。

hash的特点:

  1. hash虽然出现在URL中,但不会被包括在HTTP请求中。它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面。
  2. 可以为hash的改变添加监听事件:
window.addEventListener("hashchange", function(){}, false)
  1. 每一次改变hash(window.location.hash),都会在浏览器的访问历史中增加一个记录

通过修改hash值,并使页面作出相应处理,则可实现改变路由,同时不重新加载页面,修改hash的方法:

  1. 实现HashHistory.push()
function pushHash (path) {
  window.location.hash = path // 直接修改hash值
}
  1. 实现HashHistory.replace(),它并不是将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由
function replaceHash (path) {
  const i = window.location.href.indexOf('#') // 地址字符串中'#'的位置
  window.location.replace(
    window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
  ) // 把地址替换成我们想要的地址
}
  1. 手动修改地址栏上的地址, 监听方法:
 window.addEventListener('hashchange', () => {});

HTML5History

  • History interface是浏览器历史记录栈提供的接口,通过back(), forward(), go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。从HTML5开始,History interface提供了两个新的方法:pushState(), replaceState()使得我们可以对浏览器历史记录栈进行修改.
  1. window.history.pushState(stateObject, title, Url)

state object — 状态对象是一个由 pushState()方法创建的、与历史纪录相关的JS对象。当用户定向到一个新的状态时,会触发popstate事件。事件的state属性包含了历史纪录的state对象。(译者注:总而言之,它存储JSON字符串,可以用在popstate事件中。)state 对象可以是任何可以序列化的东西。

title — 火狐浏览器现在已经忽略此参数,将来也许可能被使用。考虑到将来有可能的改变,传递一个空字符串是安全的做法。当然,你可以传递一个短标题给你要转变成的状态。(现在大多数浏览器不支持或者忽略这个参数,最好用null代替)

URL — 这个参数提供了新历史纪录的地址。请注意,浏览器在调用pushState()方法后不会去加载这个URL,但有可能在之后会这样做,比如用户重启浏览器之后。新的URL不一定要是绝对地址,如果它是相对的,它一定是相对于当前的URL。新URL必须和当前URL在同一个源下;否则,pushState() 将丢出异常。这个参数可选,如果它没有被特别标注,会被设置为文档的当前URL。

eg:

var state = { 'page_id': 1, 'user_id': 5 };
var title = 'Hello World';
var url = 'hello-world.html';

window.history.pushState(state, title, url);

// 然后查看 window.history,会发现History的length +1, state里有上一个state的内容
  1. window.history.pushState(stateObject, title, Url) 用法同上,结果length不变,state里为替代的state的内容
  • 这两个方法有个共同的特点:

当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会立即发送请求该URL(the browser won't attempt to load this URL after a call to pushState()),这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

  • 这种方法又是如何监听手动输入地址栏的呢?答案如下:
  window.addEventListener('popstate', e => {});
  • 如何判断浏览器是否支持HTML5的新特性:
function judgeHtml5Support() {
  const ua = window.navigator.userAgent
  if (
    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
    ua.indexOf('Mobile Safari') !== -1 &&
    ua.indexOf('Chrome') === -1 &&
    ua.indexOf('Windows Phone') === -1
  ) {
    return false
  }
}

两者比较

  • history.pushState()相比于直接修改hash主要有以下优势:

    • pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
    • pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
    • pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
    • pushState可额外设置title属性供后续使用
  • history模式的问题:
    用户直接在地址栏中输入并回车,浏览器重启重新加载应用,history模式会将URL修改得就和正常请求后端的URL一样,在此情况下重新向后端发送请求,如后端没有配置对应的路由处理,则会返回404错误。官方推荐的解决办法是在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。同时这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html文件。为了避免这种情况,在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。或者,如果是用 Node.js 作后台,可以使用服务端的路由来匹配 URL,当没有匹配到路由的时候返回 404,从而实现 fallback

参考

  1. https://zhuanlan.zhihu.com/p/27588422
  2. https://developer.mozilla.org/zh-CN/docs/