項目中可能需要統計線上人數,也可能需要在使用者在退出時進行使用者登出登入,既為統計實時線上人數,也為及時清理暫時不再使用的session,節約資源提高性能。
對于以上的情況,若使用者使用頁面的登出按鈕登出,那一定萬事大吉了。當實際中這種可能性很小的,直接關閉浏覽器标簽或整個浏覽器以及其他意外情況都可能是使用者離開的方式。對此,首先的想到的是為這些情況綁定登出事件回調函數,在浏覽器視窗關閉時執行。
一開始,也是這樣幹的,效果還不錯,實作了我想要的效果。但沒多久,同僚報告了一個bug:在重新整理界面後,所有資料都丢失了。于是我去解決這個bug,當時估計是session丢失了,先從前端檢查重新整理後是不是重新拿了新的session,然後發現并沒有(可參見Servlet 中 session 的建立、銷毀及監聽了解更多關于session)。然後去背景檢視日志,發現原來重新整理之後進行了登出操作,這時大概知道問題出在何處了,注掉關閉視窗登出的代碼。問題解決。于是深入研究了一下,于是有本文。
在重新整理或關閉時會調用onbeforeunload和onunload事件,隻是重新整理事件最後會調用onload加載頁面,之前的登出回調函數就綁定在onbeforeunload事件上。兩者的差別在于onbeforeunload在onunload之前執行,它還可以阻止onunload的執行。onbeforeunload在準備向伺服器請求新的頁面,但還未開始讀取時調用;而onunload則已經從伺服器上讀到了需要加載的新的頁面,在即将替換掉目前頁面時調用,其無法阻止頁面的更新和關閉。
也就是說,無法單純的調用onbeforeunload或onunload事件區分是重新整理還是關閉了浏覽器。
于是找到了類似下面這樣的代碼
//以下代碼不會生效
window.onbeforeunload=function (){
alert("===onbeforeunload===");
if(event.clientX>document.body.clientWidth && event.clientY < || event.altKey){
alert("你關閉了浏覽器");
}else{
alert("你正在重新整理頁面");
}
}
但完全不起作用(至少在試驗的浏覽器上不起作用,故不知為什麼之前會出現這個Hack技巧)。此時的event并未定義的clientX、clientY屬性,也沒有的pageX、pageY屬性,這些屬性隻有在在滑鼠事件時發生,而此時觸發的是onbeforeunload或onunload事件。而且在關閉視窗時,無法保證兩個事件會被正确的調用的,因為視窗關閉丢失了代碼執行的環境。
好了,這種方法宣告失敗。轉念一想,我們的目标其實是區分重新整理和關閉兩個事件那我區分重新整理事件好了。同樣,重新整理的方式也有很多種,一一識别出來處理就好了,于是有了下面一段代碼。
$(document).ready(function() {
var isRefresh = false;
//使用快捷鍵重新整理,F5、Ctrl+F5、Ctrl+R
$(document).keydown(function(e) {
if ((e.which || e.keyCode) == ) {
isRefresh = true;
} else if ((e.which || e.keyCode) == && e.ctrlKey) {
isRefresh = true;
} else if ((e.which || e.keyCode) == && e.ctrlKey) {
isRefresh = true;
}
});
//滑鼠右鍵重新整理
$(document).mousedown(function(e) {
switch ((e.which || e.keyCode)) {
case :
isRefresh = true;
break;
default:
isRefresh = false;
break;
}
//可是怎麼屏蔽工具欄的重新整理按鈕呢???
$(window).on('beforeunload', function() {
if (isRefresh) {
return ;
}
return;
});
});
最終的結果也是失敗。
浏覽器工具欄的重新整理按鈕屬于浏覽器自己的内容,目前無法被頁面的JS代碼所處理,這樣就使為什麼在點選TAB标簽關閉視窗時找不到clientX等屬性的原因。JS代碼能處理的事件目前隻限于document文檔。
綜上,我們無法再前端區分使用者的重新整理還是關閉了浏覽器。但我們還是有方法完成我們的目标,畢竟有實際的解決方案在用。那就是需要借助session逾時來處理,但這無法做到非常實時,但可以将逾時時間設定短一些,同時使用空請求一直保持與背景j互動。具體請參看文章:Servlet 中 session 的建立、銷毀及監聽