pagehide 不能直接替代 unload,仅在需兼容 bfcache 时使用;event.persisted 为 true 表示页面将缓存,应冻结状态;为 false 才可清理或上报。

pagehide 不能直接替代 unload,它只在你明确需要兼容 bfcache(往返缓存)时才值得用——否则 beforeunload + sendBeacon 更稳。
为什么 pagehide 的 persisted 属性必须检查
不读 event.persisted 就直接保存状态,会导致每次用户点返回键都重复写入 localStorage、发 Beacon,甚至覆盖用户刚输入的草稿。Chrome 和 Safari 在支持 bfcache 的场景下,event.persisted 为 true 表示页面将被冻结驻留内存,下次 pageshow 会复用当前 JS 上下文;只有 false 才代表真要卸载。
-
event.persisted === true:暂停定时器、冻结动画帧、不清理 DOM 或发请求 -
event.persisted === false:才是调用navigator.sendBeacon()、saveDraft()、清临时变量的时机 - iOS Safari 对 bfcache 限制变严,
persisted为true的概率正在下降,别假设它总出现
pagehide 中哪些操作大概率静默失败
pagehide 触发时,页面可能已进入冻结态(尤其 iOS),很多同步或异步行为会被浏览器直接忽略或中止:
- 不要调用
alert()、confirm()、window.open()—— 全部无效 - 避免长耗时同步操作,比如大对象
JSON.stringify(),可能阻塞冻结流程 -
fetch()和XMLHttpRequest在persisted === true场景下基本被中止,改用navigator.sendBeacon() - 别依赖
setTimeout()或Promise.then()后续执行 —— 任务队列可能已被暂停
如何和 freeze 事件配合防漏保存
iOS Safari 和新版 Chrome 会先触发 freeze 事件(JS 执行被暂停),再发 pagehide。单靠 pagehide 有风险:如果 freeze 没触发(比如某些 Android WebView),pagehide 就是唯一机会。
- 监听
freeze时立即调用saveState(),并设标记frozen = true - 在
pagehide回调里加判断:if (!frozen && !event.persisted),补一次保存 - 可配合
document.visibilityState === 'hidden'做二次确认,过滤掉误触发(如 App 切后台)
为什么不能删掉 unload 改用 pagehide
unload 虽然在移动端不可靠,但它仍是唯一能保证「页面资源销毁前 JS 环境完整」的钩子:DOM 可读、闭包变量可访问、navigator.sendBeacon() 可靠执行。pagehide 不覆盖所有 unload 场景,尤其当页面因刷新、关闭标签页而彻底卸载时,persisted 为 false 的时机比 unload 略早,但更不确定 —— 它不保证 JS 执行环境未被冻结,也不保证异步微任务能跑完。
真正容易被忽略的是:一旦你在页面里注册了 beforeunload 监听器,bfcache 就自动失效,pagehide 的 persisted 将永远为 false。所以,要么不用 beforeunload,要么确保它只在必要时动态添加/移除。











