pagehide 不能单独替代 unload,必须配合 freeze 事件;freeze 是唯一稳定捕获页面冻结前最后一刻的事件,pagehide 仅在 persisted===false 时作终局保障,三者组合才跨平台可靠。

window.onpagehide 可以替代 onunload,但仅当你要保存的状态必须在页面**可能进入 bfcache**(即后退/前进缓存)前完成时才真正可靠;onunload 一触发,bfcache 就被彻底禁用,状态保存机会反而更少。
pagehide 事件触发时机与 persisted 判断
移动端页面离开时,pagehide 比 unload 更早触发,且浏览器仍有机会将页面存入 bfcache。关键在于通过 event.persisted 区分两种场景:
-
event.persisted === true:页面即将被缓存,此时 JS 仍可执行,是最后的同步保存窗口 -
event.persisted === false:页面将彻底卸载(如刷新、关闭、跳转外链),需做最终持久化 - 注意:
persisted是PageTransitionEvent的属性,只在pagehide和pageshow中可用,unload里没有
为什么不能只靠 pagehide?要配合 freeze 事件
在 iOS Safari 和部分 Android Chrome 中,页面进入后台后可能被冻结(freeze),而 pagehide 在某些冻结路径下根本不会触发——尤其是启用了 bfcache 且用户未主动导航时。
-
freeze是唯一能**稳定捕获冻结前最后一刻**的事件,DOM 和 JS 上下文都还完整 -
pagehide在冻结场景下可能静默跳过,别把它当兜底 - 实操建议:两个事件都要监听,但保存逻辑优先放在
freeze中;pagehide仅用于补漏(比如persisted === false时强制落盘)
保存逻辑必须轻量,否则会被截断
浏览器给 freeze 和 pagehide 的执行时间极短(通常
- 只用
localStorage.setItem()或sessionStorage.setItem(),避免indexedDB写入(异步且不可控) -
JSON.stringify()前务必浅拷贝数据,并过滤掉函数、DOM 节点、循环引用等无法序列化的值,否则直接抛错退出 - 必须包
try/catch,且 catch 后不 re-throw —— 浏览器不会等你处理错误 - 示例:
document.addEventListener('freeze', () => { try { const data = { scrollY: window.scrollY, draft: document.getElementById('editor')?.value?.slice(0, 1000) || '' }; localStorage.setItem('myapp-state', JSON.stringify(data)); } catch (e) { // 忽略,不能让错误阻断冻结流程 } });
resume 里不要 reload,也不要重复初始化
resume 不代表页面重启,而是从冻结态唤醒:DOM 树、事件监听器、未 resolve 的 Promise 全部还在,只是 JS 时间停摆了。
- 检查
document.visibilityState === 'visible'和document.hasFocus(),确认用户确实回到了前台 - 适合做轻量恢复:重置倒计时、校验 token 过期时间、拉取增量数据
- 不要在这里调用
location.reload()或重新绑定所有事件——会导致监听器重复注册、定时器叠加、状态错乱 - 更危险的是:如果
freeze时没保存完整状态,resume里也补不回来,因为 JS 执行环境已“冻结”过一次
最易被忽略的一点:pagehide 的可靠性高度依赖浏览器实现,iOS Safari 对 bfcache 支持最激进,但部分安卓 Chrome 版本会静默跳过它;真正跨平台稳定的保存入口,只有 freeze + visibilitychange(作为辅助)+ pagehide(仅作 persisted === false 时的终局保障)三者组合。

















