浏览器 bfcache 是什么?为什么页面返回时可能不重新加载?
简化版
bfcache 是浏览器的“往返缓存”,用户从页面 A 跳到 B 后再点返回,浏览器可能直接恢复 A 的完整页面状态,而不是重新请求和重新执行脚本。它能显著提升返回速度,但页面要正确处理 pageshow、pagehide、定时器、连接和过期数据。
详细版
bfcache 保存的是页面快照,包括 DOM、JS 堆、滚动位置和部分运行状态。用户通过前进/后退导航回到页面时,浏览器可以从快照恢复,体验接近瞬间打开。
需要注意:
- 返回页面可能不触发普通的重新加载流程。
pageshow事件的event.persisted可判断是否从 bfcache 恢复。unload事件、未关闭的连接、某些浏览器不支持的状态可能影响进入 bfcache。- 恢复后要刷新可能过期的数据,例如订单状态、登录状态、库存。
- 页面隐藏时应暂停计时器、音视频和无用连接,恢复时再校准。
面试回答要强调:bfcache 是性能优化,不是普通 HTTP 缓存;它缓存的是页面运行状态。
完整版教学
一、bfcache 缓存的不是资源,而是页面状态
普通 HTTP 缓存关注 JS、CSS、图片、接口响应等资源是否重新下载。bfcache 关注的是整个页面能否被冻结并恢复。用户离开页面时,浏览器把 DOM 树、JS 对象、滚动位置等状态保留下来;用户点返回时,直接把页面恢复到离开前的样子。
这就是为什么有些页面返回时输入框内容还在、滚动位置也不丢,甚至 JS 变量仍然保持旧值。它的体验非常快,因为省掉了网络、HTML 解析、JS 执行和首屏渲染。
普通返回:
请求 HTML → 解析 → 执行 JS → 请求数据 → 渲染
bfcache 返回:
恢复页面快照 → 触发 pageshow → 用户继续操作
二、它为什么能显著提升返回体验
移动端用户经常在列表页和详情页之间来回跳转。如果每次返回列表都重新加载,接口、渲染和滚动恢复都会产生成本。bfcache 能把返回耗时从几百毫秒甚至几秒压到接近瞬时。
数字例子:一个商品列表首屏加载需要 HTML 100ms、接口 500ms、渲染 200ms,总计约 800ms。使用 bfcache 返回时,如果只需 50ms 恢复快照,用户感知会完全不同。尤其是列表滚动到第 80 个商品后进入详情,返回还能保持位置,这比重新拉取列表舒服得多。
| 对比项 | 普通重新加载 | bfcache 恢复 |
|---|---|---|
| 网络请求 | 通常会发生 | 通常不需要 |
| JS 初始化 | 重新执行 | 保留旧堆状态 |
| 滚动位置 | 需要恢复 | 自动保留 |
| 用户感知 | 有等待 | 接近瞬时 |
三、如何判断页面是从 bfcache 恢复的
浏览器提供 pageshow 和 pagehide 事件。pageshow 在页面显示时触发,包括首次加载和从 bfcache 恢复。若 event.persisted 为 true,通常说明页面来自 bfcache。
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
refreshMaybeStaleData()
}
})
window.addEventListener('pagehide', () => {
pauseExpensiveTasks()
})
不要只依赖 load。从 bfcache 恢复时,load 不一定重新触发,初始化逻辑如果全写在 load 里,就可能错过恢复后的校准机会。
四、哪些代码容易影响 bfcache
不同浏览器规则不完全一样,但一些模式普遍不友好:使用 unload、页面存在无法安全冻结的活动、连接或资源未合理清理、某些跨进程或权限状态等。现代浏览器更推荐使用 pagehide 替代 unload。
unload 的问题在于它语义上表示页面即将销毁,浏览器为了兼容老代码,可能不把页面放进 bfcache。pagehide 更适合处理页面进入隐藏或被冻结前的收尾。
离开页面
├─ 使用 unload:可能阻止 bfcache
└─ 使用 pagehide:更适合冻结/恢复模型
五、恢复后最怕数据已经过期
bfcache 会保留旧页面状态,这既是优点也是风险。订单列表可能显示“待支付”,但用户在另一个页面已经完成支付;库存可能已变化;登录态可能过期。返回后如果完全不刷新,页面会显示旧事实。
解决方式不是禁用 bfcache,而是在恢复时刷新关键数据或校验版本。比如 pageshow.persisted 为 true 时,只刷新订单状态、未读数、购物车数量等易变数据,不必重建整个页面。
六、定时器和时间差要重新校准
页面进入 bfcache 后可能被冻结,定时器不会按真实时间继续稳定执行。恢复后倒计时、轮询间隔、动画状态都可能不准。正确做法是用真实时间戳计算差值,而不是假设 setInterval 每秒都执行一次。
例如优惠倒计时离开时剩 300 秒,用户 5 分钟后返回。若你只靠冻结前的变量显示,可能还显示 300 秒;应该用结束时间 endAt 减当前时间重新计算。
记忆钩子:bfcache 是“页面冷冻再解冻”,不是“重新打开”;解冻后要校准时间和刷新关键事实。
七、常见误区与追问
- 误区:bfcache 就是 HTTP 缓存。 HTTP 缓存缓存资源,bfcache 缓存完整页面运行状态。
- 误区:返回页面一定会重新执行 JS。 从 bfcache 恢复时 JS 堆可能被保留,不走完整初始化。
- 误区:禁用缓存能解决所有旧数据问题。 更好的做法是恢复后刷新关键数据,而不是牺牲返回体验。
- 追问:怎么检测 bfcache 恢复? 监听
pageshow,检查event.persisted,并处理恢复逻辑。 - 追问:为什么不推荐 unload? 它可能影响页面进入 bfcache,现代页面更推荐
pagehide。 - 追问:倒计时恢复后不准怎么办? 用结束时间和当前时间重新计算,避免依赖被冻结的 interval 次数。
八、加强记忆
bfcache 用“冻住页面、返回解冻、事实校准”来记。它保存 DOM、JS 状态和滚动位置,让前进后退非常快;但恢复不等于重新加载,所以要用 pageshow/pagehide 管生命周期,避免 unload,并刷新订单、登录、库存、倒计时这类易变状态。