什么是 BFCache(前进/后退缓存)

3,166 阅读2分钟

最近在业务中遇到了一个问题:

业务需求:A 页面为一个入口页面,点击 A 页面的按钮会通过window.localtion.href 跳转到 B 页面,完成业务后,B页面通过 histror.go(-1)返回 A 页面。这时候 A 页面会通过接口请求刷新页面的状态。其中 A、B 都是 SPA 项目,但是在不同的域名上。如下图所示:

A -> B -> A

遇到的问题是返回到 A 页面,A 页面的 mounted不会重新触发。和同事一起排查了一下,发现是 BFCache 机制导致的“问题”。

什么是 BFCache

“Back-Forward Cache” 又叫 “Page Cache” 或者 “Fast History Navigation.” 这种缓存和其他缓存的概念都不大一样1

我理解的 BFCache 就是一种“快照”。浏览器解析 HTML、CSS、JS、各类媒体资源后,会把这一个结果暂存起来。用户返回的时候,浏览器可以不用重新解析 HTML、CSS、JS等等,直接将这个“快照”拿出来就可以了。这样在移动端能节省大量的资源。在交互体验上也有很大的优势。比如说在渲染懒加载的长列表的时候,返回到上一页,所有之前请求到的接口数据、DOM 节点都不会被销毁。用户点击进入长列表的某一项查看详情,点击返回的时候能够立即继续查看。这一点采用 SPA 方案的话,往往做不到2(如果你使用过 PC 端的 twitter 的话你应该知道我在说什么)。

下面是手头上的机器测试出来的结果。yes 表示 BFCache 生效。

/**
 * 2020/11/13 测试
 * +--------+---------+--------+
 * |        | default | chrome |
 * +--------+---------+--------+
 * | xiaomi |  yes    | no[1]  |
 * +--------+---------+--------+
 * | iOS    |  yes    | yes    |
 * +--------+---------+--------+
 * | huawei |  yes    | no[1]  |
 * +--------+---------+--------+
 *
 * chrome 在 Android 系统上用的是 blink 内核,根据相关资料[2],与 WebKit 的 BFCache 实现不兼容。而 iOS 上用的是 WebKit 内核。所有有的支持 BFCache 有的不支持。
 * [1] 需要打开 chrome://flags 开启 Back-forward cache (Chrome 86 Android)
 * [2] https://developers.google.com/web/updates/2019/02/back-forward-cache
 */

Q:存在 BFCache 的情况下怎么实现之前描述的业务需求

第一种最直接的方法:B 页面通过 A 页面传的回调地址来跳转而不是 history.go(-1)。缺点是会影响 history 堆栈。要看具体的业务场景。

第二种是被 Cached 的页面会有一个特殊的属性,如果是的话,reload 当前页面。尝试下来这个是最靠谱的。

  mounted() {
    window.addEventListener('pageshow', persistHandler);
    // ...
  },
 destroyed() {
   window.removeEventListener('pageshow', persistHandler);
   // ...
 }

 function persistHandler(event) {
   if (event.persisted) {
     window.location.reload();
   }
 }

查阅资料的过程中还发现有一些其他 hack,但是尝试了很多基本上都已经失效了34。还有文章讨论了 webview 的缓存。由于笔者在这方面知积累不够。有机会在做探讨。

其他资料

测试地址 BFCache 的地址

back-forward-cache-tester.glitch.me/

Refs:

Footnotes

  1. webkit.org/blog/427/we…

  2. github.com/facebook/re…

  3. github.com/LeuisKen/le…

  4. developer.mozilla.org/en-US/docs/…