Back Forward Cache, 你?
是什么
使用前进/后退缓存(BFCache),我们不会在用户离开时销毁页面,而是推迟销毁页面并暂停 JS 执行。如果用户很快返回,我们会重新显示该页面并取消暂停 JS 执行。这能让用户几乎立即完成页面导航。
你有多少次访问过一个网站,点击链接想要跳转到另一个页面,却发现它不是你想要的,于是点击了返回按钮?在这种时候,bfcache 可以显著提高上一个页面的加载速度:
Back Forward Cache(BFCache)是一种由现代浏览器实现的会话级缓存机制。它会在用户离开页面时,将页面完整地保存(包括 DOM 树、JavaScript 内存状态、滚动位置等),并在用户点击“返回”或“前进”按钮时,无需重新加载页面资源即可瞬间恢复页面状态。
与传统的页面刷新不同,BFCache 不会触发 load/unload
事件,而是引入了新的事件机制。
生命周期事件
pagehide
:页面被隐藏(导航离开、进入 BFCache)时触发,event.persisted
标志是否进入了 BFCache。pageshow
:页面被显示(包括首次加载与从 BFCache 恢复)时触发,同样可通过event.persisted
判断。
1 | window.addEventListener('pagehide', (e) => { |
判断 BFCache
要判断页面是否是从 BFCache 中恢复,可在控制台观察事件回调的 event.persisted
字段
1 | window.addEventListener('pageshow', e => { |
启用条件与限制
虽然所有现代浏览器都支持 BFCache,但并不是所有页面都能被缓存。以下是常见的阻止因素:
条件 | 是否阻止缓存 | 说明 |
---|---|---|
使用 unload 事件 | ✅ | 推荐使用 pagehide 替代 |
页面使用了 WebSocket | ✅ | 必须手动关闭连接 |
有 beforeunload 处理逻辑 | ✅ | 会显式阻止 |
页面中含有 document.write | ✅ | 不兼容 |
有跨域 iframe | 有可能 | 会影响主页面的缓存资格 |
Bug 分析
在某些调试场景中会遇到这样的 bug:window
上的 click 监听器失效
页面首次加载时绑定在
window
上的click
事件正常工作,但当用户使用浏览器“返回”功能进入页面后,该事件不再触发,尽管页面看起来和离开前一模一样。
这种现象通常发生在使用 BFCache 恢复页面时,并且是由于绑定时机或对象选择不当导致。不同的浏览器内核对于 BFCache 的处理也可能导致预期行为不一致。
复现方式
浏览器: Safari
版本:18.5 (20621.2.5.11.8)
在 window
上添加 click 事件监听器,这将捕获冒泡上来的所有 click 事件。正常情况下,触发点击时将正常在控制台打印出
consol.log(e)
console.log(element)
1 | window.addEventListener('click', (e) => { |
恢复页面后,未能正常打印两个 log
1 | window.addEventListener('click', (e) => { |
- ❌ 但从 try 中
var element = document.getElementById('my-element')
开始,整个逻辑就“失联”了,连console.log(element)
都没走到; - ❌ 没有错误输出,说明 catch 也没有触发。
原因分析
事件系统在恢复后可能未重新初始化到
window
上
一些浏览器实现中,恢复自 BFCache 的页面并不会再次触发window.onload
,同时部分事件绑定可能因被优化而丢失。DOM 和 JS 堆内存状态虽保留,但监听器可能已被清理或被冻结
尤其是与安全机制(如跨文档导航隔离)有关时,window
上的监听器可能未能生效。SPA 框架的行为干扰
某些框架如 React/Vue/Next.js 会在页面恢复时重新挂载或 diff 元素,导致绑定在window
的事件错失绑定时机。
本例原因:页面已恢复但全局事件系统未恢复
在部分浏览器中,BFCache 恢复后,window 上的事件监听器失效,但不会自动报错或移除。也就是说:
• window.addEventListener(‘click’, …) 仍在内存中;
• 但恢复后,这个监听器不再被触发,仿佛丢失绑定一样。
解决方案:经过测试
推荐解决方案
为了确保事件在 BFCache 场景下也能可靠触发,建议采取以下策略:
尽量绑定到具体元素而不是 window
1 | document.addEventListener('click', handler); |
在 pageshow
中重新绑定关键事件
1 | function bindClick() { |
避免使用 unload
,改用 pagehide
1 | window.addEventListener('pagehide', (e) => { |
结语
随着浏览器对性能的不断优化,BFCache 正逐步成为影响页面状态管理和事件行为的重要因素。理解它的工作原理和限制条件,是构建现代 Web 应用所必须具备的技能。
尤其在调试“事件监听器失效”这类表面难以复现的问题时,考虑是否与 BFCache 有关,是排查方向的关键突破点之一。