JS-监听窗口关闭

JavaScript 监听窗口关闭。初学JavaScript,如有不当请指正。

接手的项目中有这样一个场景:

  1. 点击打印按钮,生成需要临时文件存储在项目内(该步骤为水晶报表生成打印文件必须进行的流程);

  2. 弹出新窗口,显示即将打印的文件名和下载按钮供用户确认;

  3. 用户点击下载按钮,浏览器启动下载;

  4. 用户关闭窗口,系统删除生成的临时文件。

原有的处理方式:

1
2
3
4
5
6
7
function window.onbeforeunload()
{
with(window.event) if (clientY<0&&clientX+40>document.body.clientWidth||altKey)
{
deletePDF(); // 为了便于表示,将原有的此处代码块写作一个函数
}
}

原有的写法在 Visual Studio 2015 中编辑器环境下会提示语法错误,但在 Debug / Run 过程中,在其他浏览器中均不会弹出错误提示,但没有跑进 deletePDF() 函数中,在 IE 的测试中部分测试环境会弹出错误弹窗提示:

JavaScript critical error at ... in ... : Syntax error

点击 Continue 后与其他浏览器类似,并不会影响文件下载,同样未进入 deletePDF() 函数。

onbeforeunload/beforeunload

查询 MDN 的 文档1onbeforeunloadbeforeunload 的处理程序。语法如下:

1
window.onbeforeunload = function(event) { ... };
1
window.addEventListener("beforeunload", function(event) { ... });

MDN 建议使用第二种语法。 根据 javascript - beforeunload Or onbeforeunload - Stack Overflow2 ,第一种语法取代原有的处理程序,第二种语法新增一个处理程序。

分别使用上述两种语法修改原有的处理函数,原有的函数中的第 3 行存在语法错误,该语句用于判断鼠标的位置,将该语句移除后将会出现多次触发 onbeforeunload 的情况:

  • 页面刚进入时

  • 点击下载按钮时

  • 关闭窗口时

以上情况将导致在文件未下载成功前,已进入 deletePDF ,临时文件已被删除无法下载。

实际上,beforeunload 会在多种情况下被触发,包括但不限于提交表单,点击链接/按钮,关闭窗口或标签,通过地址栏,搜索栏,书签等方式访问新页面。

因此,尽管 addEventListener 实现了关闭窗口的监听,却会在关闭窗口前就执行了相关操作。

尝试使用原有的处理方式中的 event.ClientY 进行下一步限制,但实际上仅仅使用 ClientY 将只能检测鼠标的工作情况,而无法检测通过快捷键关闭的情况,原有的处理方式同时检测了 alt 键,但这将导致所有的 alt 快捷键触发。且测试中 event.ClientY 并未起作用,原因尚未查明。

addEventListener/removeEventListener

addEventListener 对应的方法是 removeEventListener 。这两个方法在不支持 IE 9 以下版本。如果要在 IE9 以下版本使用,应该使用 attachEvent (实际上 MDN 不建议使用 attachEvent )。

1
2
3
4
5
6
if (window.attachEvent) {
window.attachEvent("beforeunload", deletePDF());
}
else if (window.addEventListener) {
window.addEventListener("beforeunload", deletePDF(), false);
}

经过几次尝试,结合 MDN 的文档,addEventListenerremoveEventListener 必须完全一致,也即,无法添加 window.addEventListner 而移除 button.removeEventListeneer

jQuery on/off bind/unbind

在检测关闭窗口并禁止提交触发的实践中,常用的方式还有使用 jQueryon/off bind/unbind 。其中 on/off 在 jQuery 1.7 中取代 bind/unbind 。由于项目较老旧,使用的 jQuery 1.2,故使用 bind/unbind 测试。测试中并未进入 deletePDF 函数,原因尚未查明。

1
2
3
4
5
6
7
8
$("[id$='_btnDownload']").click(function () {
$(window).unbind("beforeunload");
// $(window).off("beforeunload");
})
$(window).bind("beforeunload", function() {
//$(window).on("beforeunload", function() {
...
})

添加标志位检测

考虑添加一个监听事件,当按钮按下时变更标志位,标记此次操作触发方式。

1
2
3
4
5
6
7
8
9
10
11
var download = false;
document.addEventListener("click", function () {
download = true;
})

window.addEventListener("beforeunload", function() {
if (download)
download = false;
else
deletePDF();
})

此代码可正常工作,但未知是否可能存在这样一种情况:

  1. 用户1点击下载按钮,download = true;

  2. 用户2不进行下载直接关闭窗口,download = true;

在这种情况下,用户2的操作产生的临时文件将不会被删除。在本地使用 Start without debug 调试,通过不同浏览器打开不同窗口,此代码可以正常工作。

onunload/unload

考虑需求,并不需要实现 beforeunload 中的离开弹窗提示,即可以直接使用 unload 事件,监听 unload 将不会在按钮按下时被触发,使用 unload 似乎更简洁。

1
window.addEventListener("unload", deletePDF);

Page Lifecycle API | Web | Google Developers3 指出:使用 unload , beforeunload 作为检查页面生命周期的方式并不可靠,尤其是 unload 在移动端极不可靠,建议使用 pagehide,而 beforeunload 适用于存在用户未保存的更改时离开页面的监听,并应该在保存后移除该监听事件,不应使用其作为判断页生命周期的方法。

根据该文章的建议,使用以下代码:

(原文使用的是 const, 但经过测试,IE 10及以下版本中并不支持 const

1
2
var terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';
window.addEventListener(terminationEvent, deletePDF);

该代码在本地测试除 Firefox 以外可正常使用,而该项目没有移动端需求,目前尚不能看出使用 unload 的劣势。

参考资料

  1. 页生命周期:DOMContentLoaded, load, beforeunload, unload

  2. javascript - How to capture the browser window close event? - Stack Overflow

  3. Window: beforeunload event - Web APIs | MDN

  4. javascript - disable js beforeunload if button with specific id has been clicked - Stack Overflow

  5. EventTarget.removeEventListener() - Web APIs | MDN

  6. EventTarget.addEventListener() - Web APIs | MDN

  7. javascript - correct usage of addeventlistener attachevent - Stack Overflow

  8. [jQuery] jQuery 實作離開網頁或表單前向使用者確認,以防止使用者誤觸離開按鈕 | 分享你的 Coding 新鮮事 - 點部落

  9. javascript - Narrow Down BeforeUnload To Only Fire If Field Is Changed or Updated - Stack Overflow

  10. Window: unload event - Web APIs | MDN

  11. javascript - capture close window by beforeunload but exclude asp button - Stack Overflow


  1. WindowEventHandlers.onbeforeunload - Web APIs | MDN↩︎

  2. javascript - beforeunload Or onbeforeunload - Stack Overflow↩︎

  3. Page Lifecycle API | Web | Google Developers↩︎