一、bfcache 基本概念
现代浏览器在根据历史记录进行前进/后退操作时,会启用缓存机制,名为“bfcache”(back-forward cache,往返缓存),它使页面导航非常快。直到用户关闭浏览器,这个缓存状态才会被删除。
从 MDN 上得知,支持 bfcache 特性的 Firefox 浏览器,对于一个简单的浏览器会话,会缓存全部页面到内存中,包括他们的JavaScript状态。直到用户关闭浏览器,这个缓存状态才会被删除。bfcache 是一种浏览器优化,但是 HTML 标准中并未指定浏览器如何进行缓存,因此不同的浏览器的缓存策略也与 Firefox 的不同。
MDN 上还指出一些Firefox不会缓存页面的情况,如下:
- 页面绑定了 unload 或 beforeunload 事件;
- 页面设置“cache-control: no-store”;
- 站点是HTTPS且页面至少存在一个如下设置:
- “Cache-Control: no-cache”
- “Pragma: no-cache”
- “Expires: 0”或赋予“Expires”一个相对于“Date”的过去日期(除非“Cache-Control: max-age=”也定义了)
- 当用户导航离开页面时,该页面没有完全加载,或因为其他原因等待网络(例如,XMLHttpRequest);
- 页面执行IndexedDB事务;
二、不同浏览器中的不同策略
1. 测试设计
测试页面有两个,代码分别如下。
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Page 1</title> </head> <body> <img src="//static.symind.top/images/fruit.png"> <p>Page 1.</p> <a href="page2.html">Go to page 2.</a> <script> var p = document.createElement('p'); document.body.appendChild(p); var i = 0; var time = setInterval(function () { p.innerText = i++; }, 1000); window.onload = function () { alert('onload...'); } </script> </body> </html>
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Page 2</title> </head> <body> <p>Page 2.</p> <a href="javascript:goback()">后退</a> <script> function goback () { window.history.back(); } </script> </body> </html>
2. 测试结果
浏览器 | 版本 | 是否缓存 DOM | 是否缓存页面中的静态资源 | 是否缓存 js 执行状态 | 是否触发 load 事件 |
Desktop Chrome | 60.0.3112.101(正式版本)(64 位) | 否 | 是 | 否 | 是 |
Desktop Firfox | 58.0.2(64位) | 是 | 是 | 是 | 否 |
QQ浏览器 | 8.2.0.3950(安卓版) | 是 | 是 | 是 | 否 |
注意:缓存 js 执行状态表示离开当前页面时,js 将停止执行,返回后会继续执行。现在的很多手机浏览器并非使用 bfcache 特性来提高页面导航速度,而是将每个页面都封装成类似于一个 WebView 的形式,如,小米手机浏览器。而使用这种方式的浏览器,即使离开页面后,js 仍会继续执行,且不会触发下面介绍的 pageshow 与 pagehide 事件。
备注:在 Desktop Chrome 触发了“往返缓存”时,在开发者工具中能看到 HTTP 请求的状态码(Status Code)为 200 OK (from disk cache),如下图。
三、新的事件
尽管由于内存中保存了整个页面的状态,不触发load事件也不会导致什么问题,但为了更形象地说明 bfcache 的行为,浏览器还提供了一些新事件 pageshow 和 pagehide。
1. pageshow 事件
该事件与 load 事件相似,但是它在页面的每次加载时都会触发(当页面触发 bfcache 特性使用缓存加载时 load 事件不会被触发)。页面第一次加载时,pageshow 事件紧随着 load 事件之后触发。pageshow 使用一个布尔类型属性名为 persisted,在初始化加载时它的值为 false。当页面并非初始化加载时,它的值为 true(话句话说,当页面被缓存时它的值为 true)。
2. pagehide 事件
如果你想在用户导航离开页面时定义一些行为,但你不想使用 unload 事件(这会导致页面不会被缓存),你可以使用新的 pagehide 事件。与 pageshow 事件相同,pagehide 事件也使用一个布尔类型属性名为 persisted。如果页面没有被缓存,该属性的值为 false,如果页面被缓存,该属性的值为 true。当该属性的值为 false 时,unload 事件处理器进行处理,反之,pagehide 事件会在 unload 事件触发后立即触发。
四、实际业务相关
1. 业务背景简述
第一个网页提供根据城市提供服务种类列表,假定为 catePage。点击其中一种服务种类将跳转至该服务种类下的详细服务列表,假定为 servicePage。其中在 servicePage 中可以切换城市,业务要求在 servicePage 切换城市返回到 catePage 时要求刷新该页面的城市信息。如下图所示。
2. 解决方案
解决的思路很简单,当页面处于非初始化加载时,进行相关的进行更新城市信息的操作。
对于使用 bfcache 的浏览器可以添加 pageshow 事件,并结合该事件中的 persisted 属性在对于非初始化加载的情况下进行对城市信息的相关处理。
而对于小米浏览器这样,将每个页面都封装成类似于一个 WebView 的形式的手机,使用 visibilitychange 事件。该事件配合 load 事件能够判断是否为初始化加载,配合 document.hidden 属性能够判断页面显示状态。
参考文章
1. Using Firefox 1.5 caching:https://developer.mozilla.org/en-US/Firefox/Releases/1.5/Using_Firefox_1.5_caching
2. 浏览器前进/后退缓存(BF Cache): http://harttle.land/2017/03/12/backward-forward-cache.html
3. Can I use:https://caniuse.com/#search=pageshow