History API与浏览器历史堆栈管理

时间:2022-03-11 00:49:37

移动端开发在某些场景中有着特殊需求,如为了提高用户体验和加快响应速度,常常在部分工程采用SPA架构。传统的单页应用基于url的hash值进行路由,这种实现不存在兼容性问题,但是缺点也有--针对不支持onhashchange属性的IE6-7需要设置定时器不断检查hash值改变,性能上并不是很友好。

而如今,在移动端开发中HTML5规范给我们提供了一个History接口,使用该接口可以*操纵历史记录。本文并不详细介绍History接口,而是探究History接口如何影响浏览器历史堆栈,并且利用这个规律应用到具体的实际业务中,提出两种历史记录保存策略,使路由逻辑更清晰,让SPA更容易。

History API回顾

HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。

pushState

history.pushState(stateObject, title, url),包括三个参数。

第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取。

第二个参数是标题,目前浏览器并未实现。

第三个参数则是设定的url。一般设置为相对路径,如果设置为绝对路径时需要保证同源。

pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶。

在这里笔者使用历史堆栈和当前指针,用以说明浏览器对历史记录的管理策略。文档中并没有使用这样的词汇,笔者为了更形象的介绍接口对浏览器历史记录的影响,使用这样的描述,如有不当之处请及时指出(不过目前以这套模型为基础的逻辑实现中并未出现悖论)。

replaceState

该接口与pushState参数相同,含义也相同。唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url。需要注意的是,replaceState不会改动浏览器历史堆栈的当前指针。

onpopstate

该事件是window的属性。该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针。在不改变document的前提下,一旦当前指针改变则会触发onpopstate事件。

History API与业务实践

最常见的单页应用场景:列表页、商品详情页以及其内部的其他链接入口如图片页、评论页及其推荐其他商品详情页。以上提到的已经涉及到了4个单独业务逻辑页面(推荐的商品可复用商品详情页逻辑),分别是:列表、详情、图片详情和评论。将这4个页面合并到一个页面中,这就是最简单的SPA。为了用户的良好体验,必须设计合理的交互逻辑,最直观的就是浏览器(或手机app、微信公众号)的后退前进必须合乎业务逻辑特点。因此,这就涉及到了History API的使用,也牵扯到浏览器的历史记录管理。

上图为具体的逻辑示意图。在列表页,点击其中一个商品,这里是商品1,进入详情页。详情页包括了该商品的轮播图、商品的图片详情入口、评论入口和推荐的其他商品入口。接下来进行如下操作:进入图片详情页,后退至详情页再进入评论页;后退至商品1详情页再由推荐商品入口进入商品9详情页,同样在商品9详情页进入图片详情页和评论页,再后退至商品9详情页;由推荐商品入口进入商品34详情页,再进行类似操作。最后保证在商品34图片详情页或评论页可以顺利后退至最初的商品列表页。

上文中加粗的“后退”,意味着使用浏览器后退按钮,或者使用手机自带的返回,再或者使用页面上提供的后退按钮。

这样一个很细小的需求,但是一旦真正放手去做却不是那么容易。仅仅根据History API的2个函数和1个事件去盲目的尝试实现,这属于盲人摸象,鲁棒性不高。不清楚浏览器的历史记录管理策略,不了解当前页面的历史记录数量,此种情况若要实现上述场景就有些麻烦。所以在具体动手写业务代码之前,需要搞懂History的pushState和replaceState具体如何影响历史记录栈。

探究浏览器历史记录策略与History API的关系

由于浏览器并未针对每个页面的历史记录提供具体访问的接口,因此所有的测试都是黑盒。但是在移动端的中,大都是webkit内核,其webcore的具体实现也都相近,因此该节得出的结论完全可以在移动端使用。

尽管无法访问当前页的历史记录栈,但是浏览器却提供了history.length属性,它标明了当前历史记录栈的个数。该值会帮助我们更好地分析History API对历史记录栈的影响。