让浏览器前进后退更快:理解与调试 bfcache
前进后退缓存保存页面快照,让用户返回时几乎无需重新请求;正确处理生命周期、敏感状态和性能统计,才能安全地用好它。

前进后退缓存(back/forward cache,简称 bfcache)是浏览器的一种内存优化。用户离开页面时,浏览器不立即销毁页面,而是暂停 JavaScript;如果用户很快返回,就恢复整个页面和 JavaScript 堆,因此导航可接近瞬时完成,也省下重复下载资源的流量。它与 HTTP 缓存不同:HTTP 缓存保存先前请求的响应,bfcache 保存的是页面状态快照。主流浏览器都实现了 bfcache,但是否命中取决于浏览器、页面状态和具体 API。
恢复页面时会发生什么
浏览器会暂停 bfcache 中页面的计时器、未完成的 Promise 和大多数任务;页面恢复后再继续执行。若暂停的任务持有 IndexedDB 事务等跨标签页共享资源,就可能阻塞同源其他标签页,所以浏览器会避免缓存处于某些共享 API 活动状态的页面。嵌入式 iframe 通常不能独立进入 bfcache,但当主框架恢复时会随页面一同恢复;iframe 使用阻止缓存的 API,也可能影响主页面。单页应用内部的“软导航”不触发 bfcache,不过从其他页面返回 SPA 时仍可能恢复原有应用状态。
观察恢复最常用的是 pageshow 与 pagehide。pageshow 在首次加载和 bfcache 恢复时都会触发,event.persisted 为 true 表示这次显示来自 bfcache。Chromium 的 Page Lifecycle API 还提供 freeze 和 resume,但后台标签页也可能触发它们;要统计 bfcache 命中,应以 pageshow 的 persisted 为准。
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
refreshPageState();
}
});
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
closeTemporaryConnections();
}
});
pagehide.persisted === true 表示浏览器打算缓存页面,不保证最终一定缓存;其他因素仍可能让浏览器丢弃快照。它比 unload 更适合离页清理,因为页面卸载和进入 bfcache 时都会触发 pagehide。
不要用 unload;只在确有未保存修改时监听 beforeunload
不要添加 unload 监听器。桌面版 Chrome 和 Firefox 会因此让页面失去 bfcache 资格;Safari 的行为又不可靠。移动浏览器处理方式也存在差异。把离页处理改为 pagehide,并可用响应头 Permissions-Policy: unload=() 阻止页面第三方脚本添加卸载监听器。Lighthouse 也提供相关审计项。
beforeunload 适合在用户确实有未保存内容时提醒离开,不应无条件安装监听器;保存完成后立即移除:
提示文案由浏览器控制:非空的 returnValue 会请求用户确认,但示例中的字符串不会变成自定义提示文案;具体文字由用户代理决定。此接口属于旧式机制,标准建议优先通过 preventDefault() 等方式取消事件。详见 WHATWG HTML Standard:checking if unloading is canceled 与 BeforeUnloadEvent。
function beforeUnloadListener(event) {
event.preventDefault();
event.returnValue = 'Are you sure you want to exit?';
}
onPageHasUnsavedChanges(() => {
window.addEventListener('beforeunload', beforeUnloadListener);
});
onAllChangesSaved(() => {
window.removeEventListener('beforeunload', beforeUnloadListener);
});
缓存优化不能泄露过期或敏感状态
Cache-Control: no-store 用于不应被任何缓存保留的敏感页面,例如登录后展示的私密信息;历史上它也会影响 bfcache 资格。不要为了保证返回时内容“最新”而给所有页面设置 no-store。对不含敏感内容、只需重新验证的新鲜内容,可考虑 no-cache 或 max-age=0。但 bfcache 恢复直接还原内存快照,不会因为这些 HTTP 缓存指令自动重新验证;内容可能过期时,应在恢复后通过 pageshow 更新。
购物车在用户离开后可能变化;用户退出后,公共电脑上的下一个使用者也可能按“后退”看到旧页面。恢复时需重新拉取或清除敏感状态。若确实要在恢复后重新加载,可调用 location.reload();重载会保留历史关系,但某些场景使用重定向更合适。
原文给出的一个判断示例存在语法问题:document.cookie.match(/my-cookie) 缺少正则结束分隔符。本文不沿用这段无效代码。更重要的是,不能把客户端可读 Cookie 当作授权凭据:真实会话 Cookie 通常应设置 HttpOnly,脚本无法读取;是否已登录应由受保护的服务端接口或页面数据更新来决定。以下仅表示恢复后调用应用自己的状态刷新逻辑,不实现身份验证:
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
refreshPrivateDataFromServer();
}
});
广告若需在返回时刷新,应只更新广告位,而不要通过 no-store 让整页失去缓存资格。较新的 Google Publisher Tag 会自动刷新活跃广告位;其他库可在 pageshow 中触发对应刷新逻辑。
释放跨页面共享连接
如果页面持有 IndexedDB、Web Locks、WebSocket 或 WebRTC 等可能被同源其他页面访问的资源,冻结时保留连接可能阻碍其他标签页。某些浏览器也会因为未关闭的 IndexedDB 连接、进行中的 fetch/XHR、WebSocket 或 WebRTC 连接而不缓存页面。原文当前版本指出,Chrome 149 及 Safari 不会仅因开放 WebSocket 阻止缓存,其他浏览器仍可能阻止;兼容性会随浏览器版本变化,应按目标浏览器验证。
可以在 pagehide 或 Chromium 的 freeze 阶段关闭连接,并在 pageshow 或 resume 阶段重新建立。若同时监听多个恢复事件,必须避免重复创建连接。若通过 window.open() 与另一个窗口互相引用,页面可能无法安全缓存;尽量使用 rel="noopener"。现代浏览器对 target="_blank" 默认启用 noopener,但若应用确实依赖 window.opener 或 postMessage() 控制窗口,两侧页面可能都不符合 bfcache 条件。
用开发者工具检查资格
在 Chrome 中打开目标页面,再到 DevTools 的 Application → Back-forward Cache 面板运行测试。工具会模拟离开并返回,成功时显示页面由 bfcache 恢复;失败时列出原因,并把开发者可处理的问题标为 Actionable。示例中,unload 监听器会导致失败,可改为 pagehide。Lighthouse 10.0 也加入过类似审计。浏览器工具的诊断仅说明本次环境和页面状态,不代表所有浏览器、设备或导航路径都会得到相同结果。
把 bfcache 纳入分析与性能测量
不少分析库不会把 bfcache 恢复自动记成一次新页面浏览,因此启用 bfcache 后,报表中的 pageview 数可能减少。若业务定义希望将恢复也算作访问,可在 pageshow 且 persisted 为 true 时发送单独的页面浏览事件。原文用 Google Analytics 举例,并要求使用自己的测量 ID:
gtag('config', 'TAG_ID');
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
gtag('event', 'page_view');
}
});
如需估算命中率,可为导航记录类型,再把浏览器向前/向后导航数与 bfcache 恢复数比较。重复打开浏览器、复制标签页、关闭后重新打开等情况会影响样本;bfcache 还会为节省内存而被丢弃。因此,不应期待所有 back_forward 导航都命中缓存。Chrome 提供 NotRestoredReasons 等诊断能力,CrUX 也逐步纳入导航类型数据。
启用 bfcache 也会改变性能指标分布:它替代的往往是较快的重复页面加载,测得的普通页面加载数减少,整体分布可能看起来变慢,即使用户实际体验改善。可把 TTFB 等非用户体验指标按 navigate、reload、back_forward、prerender 等导航类型分开分析。需要牢记:Navigation Timing 的 back_forward 表示一次页面加载的导航类型,并不等于从 bfcache 恢复。
Core Web Vitals 应尽量反映用户真实体验。原文介绍的近似处理方式包括:bfcache 恢复后的 LCP 可用 pageshow 时间戳与下一帧绘制时间的差值估算(此时 LCP 与 FCP 相同);INP 与 CLS 可继续使用 PerformanceObserver,但恢复时将当前值重置。web-vitals 库已支持 bfcache 恢复。正式实现仍应依照目标版本的指标定义验证采样方式。
代码与版本审查说明
- 原文恢复后 Cookie 示例中的正则缺少结束分隔符;本文标记并从代码中移除该写法。源码随后一段 Navigation Timing 对象字面量中也把属性末尾写成分号,本文未照抄这段有语法问题的示例,而以文字说明所需统计口径。
- 分析示例中的
TAG_ID是占位符,不是可用追踪 ID;发布时只能替换为所属站点的配置,并遵守站点的隐私与同意管理要求。 - 文档顶部标示最后更新 2026-07-02,页脚另标 2026-09-03 UTC;本文按页面当前读取内容整理,并将这项日期差异记录在审核说明中。
- 本文没有在浏览器、DevTools 或真实站点运行示例;文中工具步骤是对源文测试流程的翻译,不代表本稿已测试通过。











暂无评论内容