pagehide比unload更适合移动端存盘,因其兼容bfcache且通过event.persisted精准区分缓存(true)与真正卸载(false),避免重复保存;必须配合freeze事件和navigator.sendbeacon确保状态可靠固化。

pagehide 为什么比 unload 更适合移动端存盘
unload 在 iOS Safari 中触发率不足 60%,且一旦触发,bfcache(往返缓存)就彻底失效——用户点返回键时页面重刷,体验断层。pagehide 则不同:无论页面是真正关闭、刷新、跳转外链,还是被浏览器悄悄冻结进 bfcache,它都会触发,并通过 event.persisted 明确告诉你当前意图。
event.persisted === true 表示页面将被缓存,后续可通过 pageshow 快速恢复;event.persisted === false 才是真正的“最后机会”,此时必须完成关键状态固化。
这决定了你不能把所有逻辑堆在 pagehide 回调里无条件执行——否则每次切后台或切 Tab 都会重复保存,浪费 I/O 还可能覆盖用户正在编辑的草稿。
如何正确监听 pagehide 并判断是否真要卸载
核心逻辑是:只在 event.persisted === false 时执行最终存盘,其余情况可跳过或仅做轻量冻结准备。
- 用
addEventListener('pagehide', handler),别直接赋值 window.onpagehide,避免被其他模块覆盖
- 在 handler 中立即检查
event.persisted,不要依赖后续异步操作(如 setTimeout 或 Promise.then)
- 为防 Safari 在 App 切后台时过早触发
pagehide,建议加一层 document.visibilityState === 'hidden' 双重确认
- 若业务对精度要求高,可配合
freeze 事件提前捕获冻结信号:先监听 freeze 做一次存盘并标记 frozen = true,再在 pagehide 中补漏(!frozen && !event.persisted)
为什么必须用 navigator.sendBeacon 而不是 fetch
fetch 和 XMLHttpRequest 在 pagehide 中大概率被中止,尤其当 event.persisted === true 时——浏览器已开始冻结 JS 执行环境,网络请求队列会被丢弃。
navigator.sendBeacon 是唯一被浏览器保障的“临终上报”机制:它把数据入队后立即返回,不等待响应,即使页面已关闭、JS 已停摆,浏览器仍会在后台尽力发出请求。
- 只支持
POST 方法,且 payload 限制通常为 64KB(实际以浏览器为准)
- 只能发文本或
ArrayBuffer,不支持 FormData 或带自定义 header 的请求
- 发送前务必序列化好数据,例如
navigator.sendBeacon('/log', JSON.stringify(data))
- 不要指望它能返回结果,也别在它后面写依赖响应的逻辑
哪些操作在 pagehide 里一定不能做
页面进入 pagehide 阶段时,UI 已失活、JS 执行可能被暂停、资源回收已启动。以下操作基本无效或危险:
-
alert()、confirm()、window.open():全部静默失败,iOS Safari 尤其严格
- 大体积同步操作,如
JSON.stringify(largeObject):可能阻塞冻结流程,导致页面卡死或被系统强杀
- 依赖微任务队列的逻辑(如
queueMicrotask 后续回调):队列可能被清空,回调永不执行
- 修改 DOM 或触发重排:无意义,页面已不可见,且可能引发异常
- 启动新定时器(
setTimeout / setInterval):不会执行,或执行时机完全不可控
真实场景中,最易被忽略的是「冻结早于 pagehide」这一事实。iOS Safari 和新版 Chrome 会先发 freeze,再发 pagehide;如果只守着后者,就可能错过最佳存盘窗口。
addEventListener('pagehide', handler),别直接赋值 window.onpagehide,避免被其他模块覆盖event.persisted,不要依赖后续异步操作(如 setTimeout 或 Promise.then)pagehide,建议加一层 document.visibilityState === 'hidden' 双重确认freeze 事件提前捕获冻结信号:先监听 freeze 做一次存盘并标记 frozen = true,再在 pagehide 中补漏(!frozen && !event.persisted)fetch 和 XMLHttpRequest 在 pagehide 中大概率被中止,尤其当 event.persisted === true 时——浏览器已开始冻结 JS 执行环境,网络请求队列会被丢弃。
navigator.sendBeacon 是唯一被浏览器保障的“临终上报”机制:它把数据入队后立即返回,不等待响应,即使页面已关闭、JS 已停摆,浏览器仍会在后台尽力发出请求。
- 只支持
POST方法,且 payload 限制通常为 64KB(实际以浏览器为准) - 只能发文本或
ArrayBuffer,不支持FormData或带自定义 header 的请求 - 发送前务必序列化好数据,例如
navigator.sendBeacon('/log', JSON.stringify(data)) - 不要指望它能返回结果,也别在它后面写依赖响应的逻辑
哪些操作在 pagehide 里一定不能做
页面进入 pagehide 阶段时,UI 已失活、JS 执行可能被暂停、资源回收已启动。以下操作基本无效或危险:
-
alert()、confirm()、window.open():全部静默失败,iOS Safari 尤其严格
- 大体积同步操作,如
JSON.stringify(largeObject):可能阻塞冻结流程,导致页面卡死或被系统强杀
- 依赖微任务队列的逻辑(如
queueMicrotask 后续回调):队列可能被清空,回调永不执行
- 修改 DOM 或触发重排:无意义,页面已不可见,且可能引发异常
- 启动新定时器(
setTimeout / setInterval):不会执行,或执行时机完全不可控
真实场景中,最易被忽略的是「冻结早于 pagehide」这一事实。iOS Safari 和新版 Chrome 会先发 freeze,再发 pagehide;如果只守着后者,就可能错过最佳存盘窗口。
alert()、confirm()、window.open():全部静默失败,iOS Safari 尤其严格JSON.stringify(largeObject):可能阻塞冻结流程,导致页面卡死或被系统强杀queueMicrotask 后续回调):队列可能被清空,回调永不执行setTimeout / setInterval):不会执行,或执行时机完全不可控











