Page Lifecycle API 的 freeze 事件是 PWA 后台冻结前唯一可靠信号,必须监听以同步释放定时器、abort fetch、解绑 requestIdleCallback;pagehide 的 persisted 属性仅作补救判断,visibilitychange 不代表冻结,document.wasDiscarded 仅恢复瞬间有效。

Page Lifecycle API 是 PWA 在后台被冻结或丢弃时唯一能靠得住的信号源;不监听 freeze,就等于放弃主动清理机会,后续卡顿、内存泄漏、请求残留全都会找上门。
必须监听 freeze 做即时资源释放
页面进入 Frozen 状态前,freeze 是最后一个能同步执行 JS 的时机:DOM 还可读、定时器还没停、fetch 仍可 abort——但之后所有异步任务(包括 setTimeout、requestIdleCallback)都会被挂起或丢弃。
- 用
addEventListener('freeze', handler, { once: true })绑定,避免重复触发 - 立刻清除所有活跃定时器:
clearInterval、clearTimeout - 取消未完成的
fetch请求:检查是否已创建AbortController,调用abort() - 解除
requestIdleCallback回调(如有),避免恢复后意外执行 - 不要在此时发起新请求、修改 DOM 或写入
localStorage——浏览器可能已开始冻结流程,这些操作会被静默忽略
pagehide 的 event.persisted 不是“缓存标志”,而是补救开关
pagehide 本身不区分用户离开还是系统冻结,关键看 event.persisted:
- 为
true:说明页面可能被 bfcache 或 Frozen,但freeze未必已触发(尤其某些 Android WebView 不发该事件)→ 此刻要补做一次资源清理 - 为
false:大概率是正常卸载(Terminated),可跳过状态保存,专注释放(如删掉临时localStorage键) - Safari 在部分 iOS 版本中对
persisted返回不稳定,建议叠加判断:document.visibilityState === 'hidden'
别把 visibilitychange 当成冻结信号
visibilitychange 只反映页面是否在前台可见,和系统是否冻结无关:
- 桌面多窗口切换时,
visibilityState可能长期为'hidden',但页面完全没被冻结 - iOS Safari 某些后台场景下,页面甚至在
visibilityState === 'visible'时就被突然冻结 - 仅适合轻量操作:暂停
video、停掉Canvas动画;不要在这里触发 heavy cleanup,容易误杀正在运行的逻辑 - 真正可靠的冻结信号只有
freeze;其他事件都是辅助,不是替代
document.wasDiscarded 只在恢复瞬间有效,且只读一次
document.wasDiscarded 是个只读布尔值,仅在页面从 Discarded 状态恢复后的**首次 JS 执行时**为 true:
- 它不是实时状态标识,不能轮询,也不能在
DOMContentLoaded后再查——那时值已重置为false - 适合用于恢复逻辑:比如重载关键状态、重建 WebSocket 连接、重置全局计数器
- 不能用来判断“当前是否被丢弃”,因为丢弃发生时 JS 已终止,根本没机会读这个值
- 注意:它和
freeze、pagehide完全不同生命周期,不要混用场景
最易被忽略的一点:PWA 的 Service Worker 虽然能接管网络请求,但它无法感知页面是否被冻结;freeze 必须在主页面 JS 中监听并响应。一旦漏掉这个事件,用户切回标签页时看到的可能是卡死界面、重复请求、或残留的 loading 状态——而这些问题,在开发阶段几乎无法复现。

















