应优先监听 freeze 事件处理系统回收:此时 JS 可同步执行、DOM 可读、定时器未停摆,仅做清定时器、取消请求、存关键状态三件事;pageshow+persisted===false 需结合 localStorage 时间戳、URL 参数和 navigation type 判定丢弃重建;visibilitychange 仅用于轻量暂停,不可误作资源释放依据。
区分“被动隐藏”(如用户切到其他标签、最小化窗口)和“系统回收”(如后台冻结或丢弃),关键不在于猜状态,而在于捕获浏览器明确发出的信号。Page Lifecycle API 提供了唯一可信赖的边界事件:freeze 表示即将被冻结,pageshow + persisted === false 且无缓存痕迹则高度提示已被丢弃。中间的 hidden 状态既不安全也不充分,不能作为资源释放或状态保存的依据。
用 freeze 事件锚定“系统回收前的最后一刻”
freeze 是 JS 还能同步执行、DOM 可读、定时器尚未停摆的最终时机,它只在系统准备冻结页面时触发,与用户行为无关。只要监听到它,就代表浏览器已决定干预——不是“可能”,而是“马上”。
必须使用
绑定,避免重复清理或写入失败
在此回调中只做三件事:清除所有定时器(
/
)、取消未完成请求(用
)、持久化关键状态(
)
禁止发起新请求、修改 DOM 或调用异步存储(如 indexedDB),这些操作大概率被静默截断
用 pageshow + persisted 和辅助信号识别“已丢弃后重建”
页面被 discard 后,浏览器会彻底销毁 JS 上下文。用户重新打开时,是一次全新加载,此时
事件会触发,且
。但这本身不等于“曾被丢弃”,需交叉验证:
检查
中上次保存的时间戳,若距当前超过 3–5 分钟,基本可判定非正常挂起,而是被丢弃
读取 URL 参数(如
),该参数应在 freeze 或 pagehide(persisted=true) 中主动写入,作为“我曾存活过”的凭证
调用
,若为
或
,配合时间差,进一步佐证冷启动
对“被动隐藏”保持克制,只做轻量暂停
visibilitychange 触发时
,仅说明页面不可见,不代表资源将被回收。很多场景(如桌面多窗口切换、画中画播放)下页面长期 hidden 却未冻结,过早释放会导致体验断裂。
适合在此阶段做的只有:暂停视频/音频、停止 Canvas 动画、暂停轮询(但保留定时器 ID)
不要清空 localStorage、不取消请求、不重置表单草稿——这些动作应留给 freeze 或 pagehide(persisted=true)
可结合
辅助判断是否失去交互权,但仍不能等同于系统干预
智能补写 = 区分场景 + 原子操作 + 防重入
所谓“补写”,不是把所有数据重刷一遍,而是按需恢复最小必要现场:
冷启动(discard 后重建):从 localStorage 拉取最近一次 freeze 保存的快照,仅还原 scrollY、编辑框内容、筛选条件等用户可感知字段
热恢复(bfcache 或 resume):不重载数据,只校验定时器是否失效、检查 token 是否过期、微调 UI 状态(如按钮 loading 态需清除)
所有恢复逻辑加防抖(如延迟 100ms 执行)并标记
标志位,避免 resume 和 pageshow 先后触发造成重复还原
{ once: true }clearIntervalclearTimeoutAbortControllerlocalStorage.setItem()pageshowevent.persisted === falselocalStorage?_reloaded=1performance.getEntriesByType('navigation')[0]?.type'reload''navigate'document.visibilityState === 'hidden'document.hasFocus() === false__restored