BFCache 页面级缓存
BFCache(Back/Forward Cache)缓存整个页面运行现场,实现前进后退秒开。
#tech / dev / frontend
#type / concept
#status / evergreen
#platform / browser
[!info] related notes
- 所属 MOC: 浏览器缓存体系
- 分类总览: 浏览器缓存分类与 304 / memory cache / disk cache
- 关联概念: 浏览器渲染流程, 页面运行时状态, 浏览器导航与前端路由
BFCache 页面级缓存
Back/Forward Cache 是浏览器为”前进/后退秒开”做的页面快照缓存。
缓存内容
BFCache 缓存的是整个页面运行现场,包括:
- DOM 状态
- JS 堆内存中的很多状态
- 滚动位置
- 表单输入内容
- 某些事件监听器状态
和 HTTP 缓存的区别
| 对比 | BFCache | HTTP 缓存 |
|---|---|---|
| 缓存对象 | 整个页面上下文 | 资源响应(CSS/JS/图片) |
| 存储位置 | 内存 | Memory/Disk |
| 触发条件 | 前进/后退 | 资源请求 |
效果
用户点击”后退”时:
- 页面像没被销毁过
- 几乎瞬间恢复
- 不需要重新请求资源
- 不需要重新执行 JS
换句话说,命中 BFCache 时,浏览器恢复的不是”某个资源缓存”,而更接近之前那份页面运行现场。
pageshow / pagehide 事件
pagehide
在页面即将被冻结进入 BFCache(或被卸载)时触发:
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
// 页面将进入 BFCache(而非被销毁)
// 可以在此清理资源
}
});
pageshow
在页面从 BFCache 恢复(或首次加载)时触发:
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// 页面从 BFCache 恢复
// 可以重新获取数据、重置定时器
}
});
实际用途
- BFCache 恢复时 JS 堆中的数据可能是过期的,用
pageshow+persisted === true重新拉取 pagehide时关闭 WebSocket 连接、清理定时器,避免阻止 BFCache
notRestoredReasons API
PerformanceNavigationTiming.notRestoredReasons(Chrome 110+)返回页面未被 BFCache 恢复的原因:
const entries = performance.getEntriesByType('navigation');
const navEntry = entries[0];
if (navEntry.notRestoredReasons) {
console.log(navEntry.notRestoredReasons);
// [{ reason: "unload-listener", src: "top-level" }, ...]
}
返回数组,每项包含:
reason:字符串,如"unload-listener"、"open-idb-connection"src:哪个 frame/origin 导致的
适合在生产环境监控 BFCache 命中率。
Cache-Control: no-store 与 BFCache
- 历史行为:某些浏览器曾将
no-store视为阻止 BFCache 的信号 - 当前行为(Chrome 86+、Firefox、Safari):
no-store不阻止 BFCache - 原理:BFCache 是页面级快照,不是 HTTP 资源缓存,
no-store的语义不适用 - 但注意:
no-store响应不会被 HTTP 缓存,页面从 BFCache 恢复后的新 fetch 请求仍然不会命中 HTTP 缓存
Chrome 实现细节
- 进入 BFCache 时,Chrome 冻结页面的 JS 执行、定时器和网络活动
- 内存限制:内存压力下 Chrome 会淘汰 BFCache 条目
- 跨域 iframe:如果页面中的 iframe 是跨域且不允许 BFCache,整个页面可能被排除(行为因浏览器而异)
Clear-Site-Data: cache头部清除 HTTP 缓存,但不影响 BFCache
不能进入 BFCache 的情况
以下因素会让浏览器认为”这个页面不适合被冻结恢复”:
unload事件监听器(最常见的原因)- 未关闭的
IndexedDB连接 - 活跃的
WebSocket、WebRTC、BroadcastChannel连接 - 运行中的
WebAssembly(某些浏览器,有可变全局变量时) - HTTP(非 HTTPS)页面(某些浏览器)
- 某些生命周期处理
- 不安全的资源占用