爱客仕-前端团队博客园

浅谈浏览器历史记录

为什么要研究浏览器历史记录,更加深入的了解单页面应有的路由

一、基本概念

  1. 浏览器历史记录管理栈
  2. 栈中的指针指向当前页面
  3. 如何生成一条历史记录
    1)点击页面中有a标签的href
    2)执行location.href = 'xxx'(location.replace('xxx')生成一条记录取代当前指针所指向的记录)
    3)表单提交跳转(注意只能跳到当前窗口)
    4)简单粗暴的方法,直接在当前页面的地址栏中输入地址
    5)使用pushState方法可以不刷新页面就可以生成一条历史记录,页面URL发生改变
    简言之,只要当页面的URL改变时,就会生成一条历史记录。在IE8及更高的版本中、Opera、Firefox、Chrome、Safari3及更高的版本中改变hash也会生成一条历史记录。
  4. 如何在浏览器中获取当前页面的历史记录状态
1
2
3
4
5
6
7
window.history
如下图所示
length:1 // 表示当前页面中会话历史元素的数目,当打开一个标签页时就会自动生成一个历史记录
scrollRestoration:"auto" // 允许web应用程序在历史导航上显示的设置默认滚动恢复行为。此属性可以是自动的(auto)或者是手动的(manual)。
这是一个实验的属性,在IE和Safari下不支持。
state:null // 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待popstate事件而查看状态的方式。
指针指向的记录如果state不为null,则显示存进去的state。

history对象

二、浏览器对历史记录的管理策略

每次增添的历史记录都会在栈的最顶端,以谷歌浏览器为例
1)点击打开浏览器的标签页,此时生成一条历史记录,并打开测试页面进行测试。每打开一个页面都会生成一条历史记录,新历史记录压人栈顶。
测试的时候一定要在当前页打开页面。测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div>
<a href="demo1.html">demo1</a>
</div>
<div>
<!--通过location.href="demo2.html"改变页面地址:-->
<button type="button" onclick="changTo(2);">href="demo2.html"</button>
</div>
<div>
<form action="demo3.html" method="get" class="dib"><button type="submit" >demo3.html</button></form>
</div>
<div>
<!--通过pushState来改变页面地址:-->
<button type="button" onclick="push4()">pushState"demo4.html"</button>
</div>

js代码如下:

1
2
3
4
5
6
function changTo(index) {
location.href = 'demo' + index + '.html';
}
function push4() {
history.pushState('hello', '', '/demo4.html')
}

点击demo1,demo2,demo3,demo4,然后再点击demo3,demo2,demo1生成的历史记录如下图
测试生成history对象

2)使用history API操作栈指针来获取历史页面

指针所在的位置会获取当前页面的state,
history.back() : 返回上一页
history.forword() : 跳转到前一页
history.go(n) : n可以为正数也可为负数,代表当前指针是向前移动还是向后移动几个位置。如果n大于或小于历史记录的数目时,指针不会发生任何变化。

下面图片的操作步骤为:先back() => go(-2) => go(-2) => go(2)
后退前进history指针变化

3)如果此时在demo3的位置点击demo1,历史记录栈会如下所示:
历史记录都是插入在栈指针所在位置的后面,前面的记录会被自动删除
后退前进history指针变化

4)历史记录超过了浏览器限制的最大条数(比如chrome、firfox为50条,IE超过了100),栈顶进入一条历史记录,则栈的底端移出去一条。

三、单页面应用中如何实现路由切换

目标:切换页面
异步传参

先介绍实现这种的两种方法:

HTML5推出的history API

三种方法:pushState replaceState onPopState

  1. pushState(state, title, URL)
    1). state: 代表一个Javascript对象,在创建对象的时候设置,可以在onpopstate和history对象中获取。(we impose a size limit of 640k characters on the serialized representation of a state object.)
    2). title: Firefox目前忽略这个参数,你可以给你的state设置一个小标题
    3). URL: 压人记录栈中URL,可选,不指定则视为当前URL。

    * 当执行pushState这个方法的时候不会去加载这个URL,但是会改变地址栏中的url。当重新加载这个页面时,URL是push进去的参数。
    * URL必须是同源链接
    
  2. replaceState(state, title, URL)参数和pushState一样,会替代当前指针指向的历史记录

  3. onPopState: 当指针在历史记录栈中已有的位置上移动时会触发,且指定的地址仍然在当前dom中。pushState和replaceState的操作不会触发这个事件
    MDN解释:

1
2
3
A popstate event is dispatched to the window every time the active history entry changes between two history entries for the same document.
The popstate event is only triggered by doing a browser action such as clicking on the back button (or calling history.back() in JavaScript).
And the event is only triggered when the user navigates between two history entries for the same document.
生成一条历史记录
1
window.history.pushState('{key: 1586}', '', '/demo1.html')
切换页面
1
2
3
4
window.addEventListener('popstate', e => {
const obj = e.state // 可获取栈指针指向的记录state
// dosometing
})warntext
异步传参

可根据state来传参

根据hash实现,hashchange来捕捉路由的变化

生成一条历史记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
const href = window.location.href
const index = href.indexOf('#')
return index === -1 ? '' : href.slice(index + 1)
}
function pushHash (path) {
window.location.hash = path
}
function replaceHash (path) {
const i = window.location.href.indexOf('#')
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
)
}
切换页面

hashchange监听后退和前进操作

1
2
3
window.addEventListener('hashchange',function(){
// dosometing
})

异步传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getParams(){
var hashArr = location.hash.split("?"),
hashName = hashArr[0].split("#")[1],//路由地址
params = hashArr[1] ? hashArr[1].split("&") : [],//参数内容
query = {};
for(var i = 0;i<params.length ; i++){
var item = params[i].split("=");
query[item[0]] = item[1]
}
return {
path:hashName,
query:query
}
}