如何利用 Page Lifecycle API 管理网页在移动端后台被冻结时的状态保存与恢复需要先看清适用场景和关键步骤,避免只记结论却忽略实际限制。
visibilitychange不可靠因页面冻结会直接暂停JS执行,freeze和pagehide(persisted=true)才是关键保存时机,pageshow需校验persisted并延迟恢复。
移动端浏览器(尤其是 iOS Safari 和 Android Chrome)在标签页切到后台后,常会直接冻结整个页面——JavaScript 停止执行、定时器失效、visibilitychange 事件可能压根不触发,或者只在极短窗口内触发就中断。你监听了 document.addEventListener('visibilitychange', ...),但发现 document.hidden 变成 true 后,代码没来得及跑完就被挂起,状态没存上。
根本原因:冻结(freeze)是比隐藏(hidden)更激进的状态,它发生在系统资源紧张或用户长时间切走时,且不保证 JS 有执行机会。所以不能依赖 visibility 状态做关键保存。
visibilitychange 是提示“用户看不见”,不是“页面要死了”freeze 事件本身也**只在未冻结时注册才有效**freeze/resume 支持较晚(iOS 16.4+ 才稳定),旧版本会静默忽略pagehide 是目前最可靠的“最后可执行时机”事件,尤其当 event.persisted === true 时,说明浏览器打算 bfcache 或冻结页面——此时必须立刻保存关键状态。
freeze 事件虽然后续可能被冻结打断,但它能让你在尚有执行能力时提前准备;两者叠加才能覆盖多数场景:
freeze,在其中调用 saveState() 并标记 frozen = true
pagehide,检查 event.persisted,若为 true 且尚未冻结,立即补存一次performance.now() 记录上次保存时间,间隔 200ms 内不重复触发let frozen = false;const saveState = () => { localStorage.setItem('uiState', JSON.stringify({ scrollY: window.scrollY, tab: 'feed' })); frozen = true;};<p>window.addEventListener('freeze', () => { if (!frozen) saveState(); });window.addEventListener('pagehide', (e) => {if (e.persisted && !frozen) saveState();});
pageshow 会在页面从 bfcache/冻结中恢复时触发,但也会在普通刷新后触发(event.persisted === false)。如果每次 pageshow 都无脑恢复状态,可能导致刚刷新完又把旧数据刷回去。
正确做法是:仅当 event.persisted === true 且状态确实存在时才恢复,并清除已恢复标记(防止多次触发):
localStorage.getItem('uiState') 是否非空,避免解析失败抛错localStorage.removeItem('uiState'),否则下次打开还会恢复(除非你明确需要持久化)pageshow 中恢复 scrollY 时,需确保 document.body 已渲染,可用 requestAnimationFrame 延迟一帧window.addEventListener('pageshow', (e) => { if (!e.persisted) return; const state = localStorage.getItem('uiState'); if (state) { try { const parsed = JSON.parse(state); requestAnimationFrame(() => { window.scrollTo(0, parsed.scrollY || 0); }); localStorage.removeItem('uiState'); } catch (e) { /* 忽略损坏的 JSON */ } }});
这是最容易踩的坑:有人试图在 freeze 里启动 setTimeout、发 fetch、轮询 localStorage,甚至监听 beforeunload ——这些在冻结状态下全部无效。冻结即暂停,JS 引擎完全停摆,连 console.log 都不会输出。
Promise.then、setTimeout、fetch().then)都不会执行beforeunload 在现代移动端基本被禁用(iOS Safari 完全移除,Android Chrome 仅限用户手势触发)真正能控制的只有两个时间点:冻结前(freeze)、切后台前(pagehide with persisted)。其余全是幻觉。