beforeunload 无法自动感知文件上传中状态,必须手动标记上传进行中并联动 isdirty 状态,在用户交互后绑定监听,且需在所有上传路径(成功、失败、超时)的最终分支统一清理状态。

beforeunload 不会响应文件上传中状态
直接监听 beforeunload 并不能感知“文件正在上传”,它只响应用户主动离开行为,且**完全不关心当前是否有异步请求在跑**。你调用 fetch() 或 XMLHttpRequest 后,浏览器不会自动标记页面为“危险状态”。即使上传卡在 99%,beforeunload 仍按常规逻辑判断——只看你是否设置了 event.returnValue,以及是否已触发用户交互。
必须手动标记“上传进行中”并联动 dirty 状态
核心不是等上传完成再解绑,而是把“上传中”当作一种未保存变更来对待。这和表单编辑的 isDirty 逻辑一致,只是触发条件换成上传生命周期:
- 点击上传按钮时,立即将全局标记(如
isUploading = true)设为true,同时设isDirty = true - 在
xhr.upload.onprogress或fetch().then()中不要改isUploading—— 进度回调不等于结束 - 只在
xhr.onload、xhr.onerror、fetch().catch()和fetch().then()的最终分支里,将isUploading = false;再检查是否所有上传都结束,才重置isDirty = false - 如果用户点了多次上传,需维护一个上传任务计数器(如
uploadCount),增减而非布尔赋值
Chrome/Firefox 会静默忽略无用户交互的 beforeunload 绑定
页面一加载就执行 window.addEventListener('beforeunload', ...),现代浏览器(Chrome 88+、Firefox 97+)大概率不弹窗。必须确保事件监听器是在用户真实操作后才挂载:
- 监听
click、input、change等可信任事件,用{ once: true }防重复 - 上传按钮的
onclick是最稳妥的时机:此时用户已点击,你可在该 handler 内首次绑定beforeunload(如果还没绑过) - 不要在
DOMContentLoaded或load里提前绑定;也不要依赖scroll或mousemove,它们在部分桌面环境不被视为“激活信号”
fetch + beforeunload 组合有隐藏陷阱
fetch 是 Promise 驱动,而 beforeunload 是同步事件。你在 fetch().then() 里设 isDirty = false 完全无效——因为事件早已触发完毕。更糟的是,若上传失败但没显式处理 catch,isUploading 就永远卡在 true,导致用户后续任何离开都被拦截。
正确做法是:所有上传路径(成功、网络错误、服务器返回 500、超时)都必须收敛到同一个清理函数,例如:
function cleanupUpload() {
isUploading = false;
if (!anyOtherUnsavedChanges()) isDirty = false;
}
然后在每个 fetch 调用后,用 .finally(cleanupUpload);对 XMLHttpRequest 则在 onload、onerror、onabort 里都调一次。
真正容易被忽略的点是:单页应用中,路由跳转(如 router.push('/home'))根本不会触发 beforeunload。你得在导航守卫里手动检查 isUploading || isDirty,并阻止跳转——这部分和 beforeunload 无关,但用户感知上是一回事。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











