在我们访问网站的时候,会存储很多浏览记录,这些浏览器记录会被存放在历史记录栈里面,我们可以使用 historyAPI 访问和操作这些浏览记录。注意,浏览记录是本次浏览器的历史记录,并不是浏览器完整的历史记录,由于涉及到用户隐私,浏览器是不可能允许我们随意查阅浏览器历史记录的。
在一些网站上可能会有前进和后退按钮,实际上这就是浏览器历史记录干的事,我们这里指的历史记录就是指本网站上的历史记录,可供我们实现前进和后退的操作,浏览器的前进和后退操作就是历史记录栈实现的,这个前进和后退操作实际上是不会刷新页面的,这一点很重要,这可以直接让我们实现单页应用效果。
现在我们应该知道,浏览器只允许我们访问本次访问的历史记录,比如从主页跳转到详情页,这是我们可以通过 historyAPI 拿到的数据,这一点务必理解清楚,这里提到的历史记录绝不是浏览器的全部历史记录。
在 window 对象上有一个 history 对象,这个对象上有很多属性,length 代表我们当前页面的历史记录数量,或者当前历史记录栈里面有多少条记录。我们历史记录栈里面可以存放一些数据,这些数据是可以通过 history.state 来读取到的,至于怎么添加,这是后面的事情。读者可以去尝试一下,例如我们点击链接 A,再从链接 A 返回回来,此时网页滚动条的位置应该是我们点击链接 A 时的位置,而不是最开始的位置,这其实是可配置的,使用 history.scrollRestoration 来配置,默认就是 auto,也就是刚才说的默认行为,如果我们希望用户返回的时候返回到网页顶部,设置为 manual 即可。
length 和 state 都是只读属性
首先,我们可以很轻松地实现前进后退以及跳转操作,使用到的方法分别是 history 对象上的 forward(), back(), go(),其中 forward() 方法可以前进,back() 方法可以后退,go() 方法传入一个数字,表示向前移动多少个页面,例如 go(2) 表示向前移动 2 个页面,go(-3) 表示后退 3 个页面。
浏览器的历史记录栈并不是无限大的,一般情况下都是 50
首先,我们要往历史记录栈里加一个网页,可以通过访问一个网页的方式去实现,我们的 JS 当然也可以动态操作,我们可以使用 pushState() 方法,这个方法接受三个参数,第一个参数为一个对象,我们前面说了,一个历史记录可以附带一些数据,这里的第一个参数就是保存这个数据的,一般是一个对象。第二个参数是一个空字符串,第三个参数就是历史记录的路径信息。
关于第二个参数,这是一个历史问题,传入空串即可。
我们使用 pushState() 加入一个历史记录之后,我们会立刻跳转到这个历史记录,并且这有一个很神奇的效果,就是我们前进后退都是不刷新页面的,这直接为我们的单页应用提供了另一种实现思路。(原来要实现单页应用需要通过 URL 中的哈希来实现)
还有一个 replaceState() 方法,可以替换当前的历史记录,参数格式和 pushState() 一模一样,但是这个方法并不是添加一个历史记录,而是替换当前历史记录,最直观的理解就是,history.length 属性的值不会发生变化,而 pushState() 会让这个属性加一。
JS 并没有提供删除历史记录的方法,我们可以自行使用
replaceState()实现
我们现在从单页应用的角度来考虑问题,我们要实现一个单页应用程序,也就是不刷新页面的应用程序,当我们点击一个按钮之后跳转到下一个目录,我们如果使用 pushState() 方法的话,他并不会向服务器发送任何请求,他只会在本地的历史记录栈里面加一个记录,页面本身不会发生任何变化,我们为了让它发生变化,当然就要在 pushState() 的时候通过 AJAX 去请求数据,这一点应该没有问题。但是前进后退就是一个问题了,如果我们加了一个前进和后退按钮,这很好办,这个按钮直接调用 back() 和 forward() 方法即可,并且在这个方法之后执行一些操作,比如说更新数据。但是用户其实是可以通过浏览器的前进和后退按钮实现的前进和后退的跳转,这就有问题了,浏览器的前进和后退毕竟不是我们自己实现的,我们必须要在浏览器的前进和后退操作的时候更新数据,这就要求我们必须要监听到浏览器的前进和后退操作。
在我们历史记录栈之间进行导航的时候,比如 go,back,forward 方法,包括浏览器的前进和后退操作的时候,都会被 window 对象的 popstate 事件监听到,该事件的事件对象中的 state 属性就可以访问到当前页面的 state 数据。有了这个事件,我们就可以在浏览器的前进和后退操作的时候完成数据更新,从而实现单页应用程序。
必须是同一文档下才可以触发该事件