onunload 经常不执行清理逻辑,因现代浏览器禁止其发起异步请求、弹窗及耗时操作,且处理时间被限制在50ms内;应改用 beforeunload + localStorage、visibilitychange 或 sendBeacon。

onunload 为什么经常不执行清理逻辑
onunload 在现代浏览器中已被严重限制:用户主动关闭标签页、刷新、导航跳转时,该事件仍会触发,但**不允许执行异步操作(如 fetch、XMLHttpRequest)、不能弹窗(alert/confirm)、无法可靠等待 Promise 完成**。Chrome 80+ 和 Firefox 90+ 还进一步缩短了事件处理时间窗口(通常 ≤ 50ms),超时即强制终止。
常见错误现象:fetch('/log?event=leave') 在 onunload 中调用后,Network 面板里看不到请求发出;或日志服务端收不到数据,但控制台无报错。
- 不要在
onunload中发起网络请求 —— 浏览器会静默丢弃 - 避免任何耗时操作(如遍历大数组、序列化复杂对象)
- 不要依赖
onunload保存关键状态(比如未提交的表单草稿)
替代方案:使用 beforeunload + 同步 localStorage 写入
beforeunload 是目前唯一能稳定捕获“即将离开页面”意图的机制,但它只允许同步操作,且必须返回字符串(用于触发浏览器原生确认弹窗)。它的核心用途不是做清理,而是**提示用户有未保存内容**。
真正需要“关闭前保存”的场景,应拆解为两步:
- 日常变更实时存到
localStorage(例如监听input或blur事件) -
beforeunload中仅检查是否有未同步的变更,若有则返回提示文本
示例:
let isDirty = false;
document.addEventListener('input', () => { isDirty = true; });
window.addEventListener('beforeunload', (e) => {
if (isDirty) {
e.preventDefault();
e.returnValue = ''; // 必须设置,否则 Chrome 不显示提示
}
});
// 页面加载时从 localStorage 恢复
const saved = localStorage.getItem('draft');
if (saved) document.getElementById('editor').value = saved;
Page Visibility API 更适合感知“非活跃”状态
如果目标是暂停定时器、停止视频播放、释放 WebSocket 连接等“页面退到后台”的行为,visibilitychange 事件比 onunload 更精准、更可靠。
它不依赖用户是否关闭页面,而反映页面是否可见(包括切换标签页、最小化窗口、锁屏等),且无执行时间限制。
- 监听
document.visibilityState:值为'visible'/'hidden' - 在
'hidden'时暂停轮询、释放资源;在'visible'时恢复 - 注意:iOS Safari 在页面不可见时可能冻结 JS 执行,
visibilitychange仍能触发,但后续代码未必运行
示例:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
clearInterval(pollingTimer);
myVideo.pause();
} else {
pollingTimer = setInterval(fetchNewData, 5000);
}
});
Service Worker 无法监听页面卸载,但可接管离线缓存
有人误以为 Service Worker 能监听客户端页面关闭 —— 实际上,ServiceWorkerGlobalScope 没有 onunload 或类似钩子。SW 生命周期独立于页面,且页面关闭时 SW 可能已进入 terminated 状态。
如果你需要“关掉页面后还能发日志”,唯一可行路径是:在页面还活着时,用 navigator.sendBeacon() 发送小量数据。
-
sendBeacon()是专为此设计的 API,支持跨域,发送异步但不阻塞卸载流程 - 只接受
POST,且 payload 限于ArrayBuffer、Blob、FormData、URLSearchParams或字符串 - 务必在
beforeunload或pagehide中调用,unload已不可靠
示例:
window.addEventListener('beforeunload', () => {
navigator.sendBeacon('/log', JSON.stringify({ event: 'close', time: Date.now() }));
});
真正棘手的点在于:没有一个 API 能 100% 保证“最后时刻的操作一定成功”。sendBeacon 有成功率,localStorage 可能被用户清空,visibilitychange 在某些系统休眠场景下不触发。设计时得接受“尽力而为”,把关键逻辑前置到用户操作过程中,而不是押注在关闭那一瞬间。

















