youngwind / blog

梁少峰的个人博客
4.66k stars 384 forks source link

浏览器history api的研究 #73

Open youngwind opened 8 years ago

youngwind commented 8 years ago

问题

在做一个单页面应用项目的时候碰到这样一个问题:用户从页面A跳转到页面B之后,不想让用户回退到页面A,无论他是点击浏览器的回退按钮还是按下键盘的backspace。 一开始的解决思路是:想办法禁止掉键盘的backspace和浏览器的回退事件。不过后来觉得这种方法治标不治本。我们看下面两张图。(注意回退按钮的不同)

图一:打开一个新的空白标签页 2016-05-17 9 03 53

图二:打开一个新的空白标签页,然后跳转到百度 2016-05-17 9 04 14

从图中我们可以看到,浏览器内部似乎有一个针对每一个标签页的浏览记录这样的一个队列,回退的时候pop一下,前进的时候push一下。所以,研究这个history 队列本身才是解决问题的根本

从前的history

window.history对象存储着当前标签页的浏览历史记录,但是脚本不能访问已保存的url(安全性问题) 考虑下面这样的情况:打开一个空白页→跳转到页面A→跳转到页面B 然后依次执行下面的脚本。

history.back();    // 页面A
history.forward();   // 页面B
history.go(-2);   // 空白页
history.go(2);    // 页面B

这样的方法在IE8也是支持的。

过渡阶段的history

随着web的发展,越来越多的应用是动态生成的,需要在无刷新页面的情况下控制导航。所以,如果想让用户依然能够通过浏览器的“后退”和“前进”按钮,那该怎么办呢? 在HTML5标准出来之后,常用的方法是location.hash和hashchange,hash就像是url的片段标识符:a.html#page1,也就是锚点,设置location.hash会更新url,并且在浏览器的历史记录中添加一条记录。每当用户“回退”或者“前进”的时候,就会触发一个hashchange事件,这个时候再去读取location.hash就可以根据hash重新显示应用的状态。

HTML5中history

HTML5将上面的hash纳到了标准当中,而且还定义了其他的方法。

history.pushState({a:'page1'},'title','a');   // 跳转到url ./a
history.replaceState({a:'page1'},'title','a');  // 跳转到url ./a 
//replaceState与pushState的区别:
//replaceState修改当前历史记录,而不是添加一条历史记录。
//也就是执行replaceState之后history.length并不会加1

react-router和history中的history

项目当中用到了react-router和history,react-router构建在history之上。其中实现的基本原理与上面的相似。所以,如果使用react-router+history的话,跳转路由的方法如下:

history.push('newUrl');  // 跳转到newUrl,新添加一条历史记录
history.replaceState(null,'newUrl');   
// 跳转到newUrl,删除之前所有的历史记录,**也就是说这就是禁止用户回退的方法**

参考资料:

  1. https://developer.mozilla.org/zh-CN/docs/DOM/Manipulating_the_browser_history
  2. http://www.zhangxinxu.com/wordpress/2013/06/html5-history-api-pushstate-replacestate-ajax/
  3. http://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html