前言
最近实施的同事报障,说用户审批流程后直接关闭浏览器,操作十余次后系统就报用户会话数超过上限,咨询4A同事后得知登陆后需要显式调用登出API才能清理4A端,否则必然会超出会话上限。
即使在页面上增添一个登出按钮也无法保证用户不会直接关掉浏览器,更何况用户已经习惯这样做,增加功能好弄,改变习惯却难啊。这时想起N年用过的window.onbeforeunload
和window.onunload
事件。
本文记录重拾这两个家伙的经过,以便日后用时少坑。
为网页写个Dispose方法
C#中我们会将释放非托管资源等收尾工作放到Dispose方法中, 然后通过using
语句块自动调用该方法。对于网页何尝不是有大量收尾工作需要处理呢?那我们是否也有类似的机制,让程序变得更健壮呢?——那就靠beforeunload
和unload
事件了。但相对C#通过using
语句块自动调用Dispose方法,beforeunload
和unload
的触发点则复杂不少。
我们看看什么时候会触发这两个事件呢?
- 在浏览器地址栏输入地址,然后点击跳转;
- 点击页面的链接实现跳转;
- 关闭或刷新当前页面;
- 操作当前页面的
Location
对象,修改当前页面地址; - 调用
window.navigate
实现跳转; - 调用
window.open
或document.open
方法在当前页面加载其他页面或重新打开输入流。
OMG!这么多操作会触发这两兄弟,怎么处理才好啊?没啥办法,针对功能需求做取舍咯。对于我的需求就是在页面的Dispose方法中调用登出API,经过和实施同事的沟通——只要刷新页面就触发登出。
1 2 3 |
;(function(exports, $, url){ exports.dispose = $.proxy($.get, $, url) }(window, $, "http://pseudo.com/logout")) |
那现在剩下的问题就在于到底是在beforeunload
还是unload
事件处理函数中调用dispose方法呢?这里涉及两点需要探讨:
beforeunload
和unload
的功能定位是什么?beforeunload
和unload
的兼容性.
beforeunload
和unload
的功能定位是什么?
beforeunload
顾名思义就是在unload
前触发,可通过弹出二次确认对话框来试图终断执行unload.
unload
就是正在进行页面内容卸载时触发的,一般在这里进行一些重要的清理善后工作,而这时页面处于以下一个特殊的临时状态:
- 页面所有资源(img, iframe等)均未被释放;
- 页面可视区域一片空白;
- UI人机交互失效(
window.open,alert,confirm
全部失效); - 没有任何操作可以阻止
unload
过程的执行。(unload
事件的Cancelable属性值为No)
那么反过来看看beforeunload
事件,这时页面状态大致与平常一致:
- 页面所有资源均未释放,且页面可视区域效果没有变化;
- UI人机交互失效(
window.open,alert,confirm
全部失效); - 最后时机可以阻止
unload
过程的执行.(beforeunload
事件的Cancelable属性值为Yes)
beforeunload
和unload
的兼容性
对于移动端浏览器而言(Safari, Opera Mobile等)而言不支持beforeunload
事件,也许是因为移动端不建议干扰用户操作流程吧。
防数据丢失机制——二次确认
当用户正在编辑状态时,若因误操作离开页面而导致数据丢失常作为例外处理。处理方式大概有3种:
- 丢了就丢呗,然后就是谁用谁受罪了;
- 简单粗暴——侦测处于编辑状态时,监听
beforeunload
事件作二次确定,也就是将责任抛给用户; - 自动保存,甚至做到Work in Progress(参考john papa的分享John Papa-Progressive Savingr-NG-Conf)
这里我们选择方式2,弹出二次确定对话框。想到对话框自然会想到window.confirm
,然后很自然地输入以下代码
1 2 3 4 5 6 |
window.addEventListener('beforeunload', function(e){ var msg = "Do u want to leave?\nChanges u made may be lost." if (!window.confirm(msg)){ e.preventDefault() } }) |
然后刷新页面发现啥都没发生,接着直接蒙了。。。。。。
坑1: 无视window.alert/confirm/prompt/showModalDialog
beforeunload
和unload
是十分特殊的事件,要求事件处理函数内部不能阻塞当前线程,
而window.alert/confirm/prompt/showModalDialog
却恰恰就会阻塞当前线程,因此H5规范中以明确在beforeunload
和unload
中直接无视这几个方法的调用。
Since 25 May 2011, the HTML5 specification states that calls to
window.showModalDialog()
,window.alert()
,window.confirm()
andwindow.prompt()
methods may be ignored during this event.(onbeforeunload#Notes)[https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Notes]
在chrome/chromium下会报”Blocked alert/prompt/confirm() during beforeunload/unload.”的JS异常,而firefox下则连异常都懒得报。
既然不给用window.confirm
,那么如何弹出二次确定对话框呢?其实beforeunload
事件已经为我们准备好了。只要改成
1 2 3 4 |
window.onbeforeunload = function(){ var msg = "Do u want to leave?\nChanges u made may be lost." return msg } |
通过DOM0 Event Model的方式监听beforeunload事件时,只需返回值不为undefined或null,即会弹出二次确定对话框。而IE和Chrome/Chromium则以返回值作为对话框的提示信息,Firefox4开始会忽略返回值仅显式内置的提示信息.
太不上道了吧,还在用DOM0 Event Model:( 那我们来看看DOM2 Event Model是怎么一个玩法
1 2 3 4 5 6 |
// Microsoft DOM2-ish Event Model window.attachEvent('onbeforeunload', function(){ var msg = "Do u want to leave?\nChanges u made may be lost." var evt = window.event evt.returnValue = msg }) |
对于巨硬独有的DOM2 Event Model,我们通过设置window.event.returnValue
为非null或undefined来实现弹出窗的功能(注意:函数返回值是无效果的)
那么标准的DOM2 Event Model呢?我记得window.event.returnValue
是 for ie only的,但事件处理函数的返回值又木有效果,那只能想到event.preventDefault()
了,但event.preventDefault()
没有带入参的重载,那么是否意味通过标准DOM2 Event Model的方式就不支持自定义提示信息呢?
1 2 3 |
window.addEventListeners('beforeunload', function(e){ e.preventDefault() }) |
在FireFox上成功弹出对话框,但Chrome/Chromium上却啥都没发生。。。。。。
坑2: HTMLElement.addEventListener
事件绑定
event.preventDefault()
这一玩法就FireFox支持,Chrome这次站到IE的队列上了。综合起来的玩法是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 13-37">37 38 39 40 41 I才能清理4A端,否则必然会超出会话上限。
即使在页面上增添一个登出按钮也无法保证用户不会直接关掉浏览器,更何况用户已经习惯这样做,增加功能好弄,改变习惯却难啊。这时想起N年用过的 本文记录重拾这两个家伙的经过,以便日后用时少坑。 为网页写个Dispose方法C#中我们会将释放非托管资源等收尾工作放到Dispose方法中, 然后通过 我们看看什么时候会触发这两个事件呢?
OMG!这么多操作会触发这两兄弟,怎么处理才好啊?没啥办法,针对功能需求做取舍咯。对于我的需求就是在页面的Dispose方法中调用登出API,经过和实施同事的沟通——只要刷新页面就触发登出。
那现在剩下的问题就在于到底是在
|
1 2 3 4 5 6 |
window.addEventListener('beforeunload', function(e){ var msg = "Do u want to leave?\nChanges u made may be lost." if (!window.confirm(msg)){ e.preventDefault() } }) |
然后刷新页面发现啥都没发生,接着直接蒙了。。。。。。
坑1: 无视window.alert/confirm/prompt/showModalDialog
beforeunload
和unload
是十分特殊的事件,要求事件处理函数内部不能阻塞当前线程,
而window.alert/confirm/prompt/showModalDialog
却恰恰就会阻塞当前线程,因此H5规范中以明确在beforeunload
和unload
中直接无视这几个方法的调用。
Since 25 May 2011, the HTML5 specification states that calls to
window.showModalDialog()
,window.alert()
,window.confirm()
andwindow.prompt()
methods may be ignored during this event.(onbeforeunload#Notes)[https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Notes]
在chrome/chromium下会报”Blocked alert/prompt/confirm() during beforeunload/unload.”的JS异常,而firefox下则连异常都懒得报。
既然不给用window.confirm
,那么如何弹出二次确定对话框呢?其实beforeunload
事件已经为我们准备好了。只要改成
1 2 3 4 |
window.onbeforeunload = function(){ var msg = "Do u want to leave?\nChanges u made may be lost." return msg } |
通过DOM0 Event Model的方式监听beforeunload事件时,只需返回值不为undefined或null,即会弹出二次确定对话框。而IE和Chrome/Chromium则以返回值作为对话框的提示信息,Firefox4开始会忽略返回值仅显式内置的提示信息.
太不上道了吧,还在用DOM0 Event Model:( 那我们来看看DOM2 Event Model是怎么一个玩法
1 2 3 4 5 6 |
// Microsoft DOM2-ish Event Model window.attachEvent('onbeforeunload', function(){ var msg = "Do u want to leave?\nChanges u made may be lost." var evt = window.event evt.returnValue = msg }) |
对于巨硬独有的DOM2 Event Model,我们通过设置window.event.returnValue
为非null或undefined来实现弹出窗的功能(注意:函数返回值是无效果的)
那么标准的DOM2 Event Model呢?我记得window.event.returnValue
是 for ie only的,但事件处理函数的返回值又木有效果,那只能想到event.preventDefault()
了,但event.preventDefault()
没有带入参的重载,那么是否意味通过标准DOM2 Event Model的方式就不支持自定义提示信息呢?
1 2 3 |
window.addEventListeners('beforeunload', function(e){ e.preventDefault() }) |
在FireFox上成功弹出对话框,但Chrome/Chromium上却啥都没发生。。。。。。
坑2: HTMLElement.addEventListener
事件绑定
event.preventDefault()
这一玩法就FireFox支持,Chrome这次站到IE的队列上了。综合起来的玩法是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |