浏览器不会自动回收页面,Page Lifecycle API 仅报告状态而非触发回收;可靠存盘信号是 beforeunload、visibilitychange 和 freeze;需防坑如私密模式 localStorage 失败、IndexedDB 异步写入不及时;自愈关键在结构化持久化与 pageshow 时按 event.persisted 区分恢复逻辑。
浏览器
不会“自动回收页面”,Page Lifecycle API 也从不触发或执行内存回收——它只报告页面当前所处的生命周期状态。所谓“自动回收瞬间”是常见误解,实际能捕获的是
、
或
这类系统级暂停信号,而真正内存释放(如 Android 后台杀进程、iOS 页面被强退)根本不可监听,也无回调。
为什么 pagehide / freeze 不等于“被回收”
触发时页面可能只是隐藏(比如切到其他 Tab),
发生时 JS 仍可运行(仅暂停定时器、网络请求等),两者都不代表内存已释放。真正的进程级回收(如 Chrome 在内存紧张时销毁后台页)完全静默,API 无法感知,更不可能在那一刻执行存盘逻辑。
Android Chrome 后台页被系统杀死时,
、
、
全部不触发
iOS Safari 对后台页限制极严,
切到 hidden 后几秒内 JS 就会停摆,但无明确事件通知
只在页面重新激活时触发,不能反推“之前是否被回收”
哪些状态变化真正值得监听并触发存盘
应聚焦在用户可感知、JS 仍可控的临界点,而非幻想捕捉“回收瞬间”。可靠信号只有三个:
:用户主动关闭/刷新/跳转前,有约 50ms 窗口执行同步存盘(必须用
或
等同步/快速操作)
+
:页面切到后台时立即存盘,覆盖部分后台被冻结场景
:作为 visibilitychange 的补充,在支持该事件的浏览器中再做一次确认存盘(注意:Safari 目前不支持
)
示例关键代码:
ZERO浏览器
一款PC端AI智能浏览器
下载
存盘动作本身容易失败的几个坑
即使监听对了事件,存盘也可能静默失败:
在私密模式下直接抛
,需 try/catch 并降级到
或内存缓存
IndexedDB 的
是异步的,
里调用来不及完成,必须用
打开后立刻
并依赖其同步写入特性(实际仍需测试兼容性)
频繁存盘(如每 keystroke)会拖慢输入响应,建议加防抖(
延迟 1s),但
必须立即触发,不能防抖
真正需要关注的“自愈”边界在哪里
所谓“自愈”不是恢复已被销毁的页面,而是让页面重载后能从持久化存储中还原状态。这要求:
所有关键状态(表单内容、滚动位置、临时选中项)必须结构化序列化,避免存
节点或函数
还原逻辑必须在
或
早期执行,且要区分是冷启动还是热恢复(
为 true 表示从 bfcache 恢复)
服务端也要配合:前端存的草稿 ID 需同步到后端,避免用户多端操作时状态冲突
最易被忽略的一点:bfcache(back-forward cache)下页面完全不销毁,
会带
,此时不该清空本地缓存,否则用户按返回键后看到的是空白页。
freezevisibilitychangepagehidepagehidefreezebeforeunloadpagehidefreezevisibilitychangepageshowbeforeunloadlocalStorage.setItem()indexedDB.open().onsuccessvisibilitychangedocument.hidden === truefreezefreezewindow.addEventListener('beforeunload', () => {
saveToLocalStorage(); // 必须是同步写入
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) saveToLocalStorage();
});
if ('onfreeze' in window) {
window.addEventListener('freeze', saveToLocalStorage);
}localStorageQuotaExceededErrorsessionStorageput()beforeunloadindexedDB.open().onsuccesstransaction.objectStore().put()setTimeoutbeforeunloadDOMpageshowDOMContentLoadedevent.persistedpageshowevent.persisted === true