性能 · 概念
前进后退缓存(bfcache)
发布于
前进后退缓存(bfcache)在用户离开页面时保存整个页面的完整快照,包括 JavaScript 堆和进行中的状态,并在之后的后退或前进导航中即时恢复而不是重新加载。恢复完全跳过网络和脚本执行,比任何 Service Worker 或 HTTP 缓存命中都快,但只有避开了一小串阻碍项的页面才有资格。
离开页面时浏览器把它冻结:定时器和待处理的 Promise 被暂停而不是取消,文档在有限时间内留在内存中。后退或前进回到该条目时页面原地解冻;pageshow 以 persisted === true 触发,load 事件不触发。若页面已被驱逐(内存压力、时间上限或某个阻碍项),该导航就是一次普通加载(Back/forward cache,web.dev)。各引擎都有 bfcache:Chrome 自 96 起在桌面和 Android 上提供,Firefox 和 Safari 则早得多。
什么让页面失去资格
Section titled “什么让页面失去资格”unload监听器。 桌面版 Chrome 和 Firefox 拒绝缓存注册了它的页面;Safari 和移动版 Chrome 可能缓存然后跳过触发该事件,这让处理函数在所有引擎里都不可靠。pagehide在unload会触发的所有场合触发,进入缓存时也触发,所以它是替代品。- 主文档上的
Cache-Control: no-store。 尽管 bfcache 不是 HTTP 缓存,浏览器一直拒绝缓存这类页面,所以该头只应出现在内容不得以任何形式存储的页面上。 - 打开的连接。 导航时仍打开的
WebSocket、WebRTC连接或 IndexedDB 事务会在 Chrome 中阻止缓存;在pagehide里关闭它们。 - 其他阻碍项。 仍然存活的
window.opener引用、待处理的beforeunload提示,以及部分版本中带 body 的进行中fetch()请求。Chrome 的清单很长,由下面的 API 按导航逐次报告。
读取页面未被恢复的原因
Section titled “读取页面未被恢复的原因”performance.getEntriesByType('navigation')[0].notRestoredReasons(Chrome 125;BCD api.PerformanceNavigationTiming.notRestoredReasons)在恢复后为 null,否则是一个对象,其 reasons 数组列出阻碍项,例如 { reason: "unload-listener" },跨源 iframe 的同一棵树被遮蔽为 null。Firefox 和 Safari 不暴露它;下面的 DevTools 测试工具覆盖开发期的 Chrome,现场 API 覆盖生产环境。
各引擎都有 bfcache:Safari 和 Firefox 已有十余年,Chrome 自 96 起在所有平台提供(Back/forward cache,web.dev)。可观察的差异在于上面的资格规则,以及只有 Chromium 暴露的 notRestoredReasons。
与 Service Worker 和 Core Web Vitals 的关系
Section titled “与 Service Worker 和 Core Web Vitals 的关系”bfcache 恢复完全不经过 Service Worker 和 HTTP 缓存,而且在 Core Web Vitals 工具中恢复被计为一次独立的页面访问、LCP 接近零,这正是 bfcache 资格能直接改善现场指标的原因。敏感状态(已登出的会话、过期的购物车合计)必须在检查 event.persisted 的 pageshow 处理函数里刷新,因为 load 不会触发。
两个示例都在页面里而不是 Service Worker 里,因为 bfcache 是文档级特性。
恢复时刷新过期状态
Section titled “恢复时刷新过期状态”pageshow 在首次加载和恢复时都运行;persisted 区分两者。处理函数重新检查会话并重新拉取新鲜度重要的数据,而不重新加载页面。
window.addEventListener('pageshow', (event) => { if (!event.persisted) return; // 普通加载:load 处理函数已经运行过 refreshSession(); refreshCartTotal();});
window.addEventListener('pagehide', (event) => { if (event.persisted) { socket?.close(); // 页面正进入 bfcache:释放连接 }});在 pagehide 中关闭 WebSocket 是页面在 Chrome 中获得资格的关键;在 pageshow 中重新打开则恢复用户期待的实时状态。
从现场上报阻碍项
Section titled “从现场上报阻碍项”在未被恢复的页面上把 notRestoredReasons 发给分析系统,生产环境里真正起作用的阻碍项(例如注册了 unload 的第三方脚本)就能被看见。
function reportBfcacheBlockers() { const [nav] = performance.getEntriesByType('navigation'); if (!nav || !('notRestoredReasons' in nav)) { return; // 不支持(Firefox、Safari):改用开发者工具的测试 } if (nav.type === 'back_forward' && nav.notRestoredReasons) { navigator.sendBeacon('/analytics/bfcache', JSON.stringify(nav.notRestoredReasons)); }}按 nav.type === 'back_forward' 过滤:其他导航类型上该属性虽然存在,但描述的不是页面本可改变的东西。
- Back/forward cache(web.dev)
- Test back/forward cache(developer.chrome.com)
- PerformanceNavigationTiming: notRestoredReasons property(developer.mozilla.org)
- Core Web Vitals(LCP、INP 与 CLS)
- Speculation Rules API
- PerformanceObserver 与绘制计时
规范
| 规范 | 状态 |
|---|---|
| 无。 | |