摘要:坑無視和是十分特殊的事件,要求事件處理函數(shù)內(nèi)部不能阻塞當(dāng)前線程,而卻恰恰就會阻塞當(dāng)前線程,因此規(guī)范中以明確在和中直接無視這幾個方法的調(diào)用。
前言
?最近實(shí)施的同事報(bào)障,說用戶審批流程后直接關(guān)閉瀏覽器,操作十余次后系統(tǒng)就報(bào)用戶會話數(shù)超過上限,咨詢4A同事后得知登陸后需要顯式調(diào)用登出API才能清理4A端,否則必然會超出會話上限。
?即使在頁面上增添一個登出按鈕也無法保證用戶不會直接關(guān)掉瀏覽器,更何況用戶已經(jīng)習(xí)慣這樣做,增加功能好弄,改變習(xí)慣卻難啊。這時想起N年用過的window.onbeforeunload和window.onunload事件。
?本文記錄重拾這兩個家伙的經(jīng)過,以便日后用時少坑。
?C#中我們會將釋放非托管資源等收尾工作放到Dispose方法中, 然后通過using語句塊自動調(diào)用該方法。對于網(wǎng)頁何嘗不是有大量收尾工作需要處理呢?那我們是否也有類似的機(jī)制,讓程序變得更健壯呢?——那就靠beforeunload和unload事件了。但相對C#通過using語句塊自動調(diào)用Dispose方法,beforeunload和unload的觸發(fā)點(diǎn)則復(fù)雜不少。
?我們看看什么時候會觸發(fā)這兩個事件呢?
在瀏覽器地址欄輸入地址,然后點(diǎn)擊跳轉(zhuǎn);
點(diǎn)擊頁面的鏈接實(shí)現(xiàn)跳轉(zhuǎn);
關(guān)閉或刷新當(dāng)前頁面;
操作當(dāng)前頁面的Location對象,修改當(dāng)前頁面地址;
調(diào)用window.navigate實(shí)現(xiàn)跳轉(zhuǎn);
調(diào)用window.open或document.open方法在當(dāng)前頁面加載其他頁面或重新打開輸入流。
?OMG!這么多操作會觸發(fā)這兩兄弟,怎么處理才好啊?沒啥辦法,針對功能需求做取舍咯。對于我的需求就是在頁面的Dispose方法中調(diào)用登出API,經(jīng)過和實(shí)施同事的溝通——只要刷新頁面就觸發(fā)登出。
;(function(exports, $, url){ exports.dispose = $.proxy($.get, $, url) }(window, $, "http://pseudo.com/logout"))
那現(xiàn)在剩下的問題就在于到底是在beforeunload還是unload事件處理函數(shù)中調(diào)用dispose方法呢?這里涉及兩點(diǎn)需要探討:
beforeunload和unload的功能定位是什么?
beforeunload和unload的兼容性.
beforeunload和unload的功能定位是什么??beforeunload顧名思義就是在unload前觸發(fā),可通過彈出二次確認(rèn)對話框來試圖終斷執(zhí)行unload.
?unload就是正在進(jìn)行頁面內(nèi)容卸載時觸發(fā)的,一般在這里進(jìn)行一些重要的清理善后工作,而這時頁面處于以下一個特殊的臨時狀態(tài):
頁面所有資源(img, iframe等)均未被釋放;
頁面可視區(qū)域一片空白;
UI人機(jī)交互失效(window.open,alert,confirm全部失效);
沒有任何操作可以阻止unload過程的執(zhí)行。(unload事件的Cancelable屬性值為No)
?那么反過來看看beforeunload事件,這時頁面狀態(tài)大致與平常一致:
頁面所有資源均未釋放,且頁面可視區(qū)域效果沒有變化;
UI人機(jī)交互失效(window.open,alert,confirm全部失效);
最后時機(jī)可以阻止unload過程的執(zhí)行.(beforeunload事件的Cancelable屬性值為Yes)
beforeunload和unload的兼容性?對于移動端瀏覽器而言(Safari, Opera Mobile等)而言不支持beforeunload事件,也許是因?yàn)橐苿佣瞬唤ㄗh干擾用戶操作流程吧。
防數(shù)據(jù)丟失機(jī)制——二次確認(rèn)?當(dāng)用戶正在編輯狀態(tài)時,若因誤操作離開頁面而導(dǎo)致數(shù)據(jù)丟失常作為例外處理。處理方式大概有3種:
丟了就丟唄,然后就是誰用誰受罪了;
簡單粗暴——偵測處于編輯狀態(tài)時,監(jiān)聽beforeunload事件作二次確定,也就是將責(zé)任拋給用戶;
自動保存,甚至做到Work in Progress(參考john papa的分享John Papa-Progressive Savingr-NG-Conf)
?這里我們選擇方式2,彈出二次確定對話框。想到對話框自然會想到window.confirm,然后很自然地輸入以下代碼
window.addEventListener("beforeunload", function(e){ var msg = "Do u want to leave? Changes u made may be lost." if (!window.confirm(msg)){ e.preventDefault() } })
然后刷新頁面發(fā)現(xiàn)啥都沒發(fā)生,接著直接蒙了。。。。。。
坑1: 無視window.alert/confirm/prompt/showModalDialog?beforeunload和unload是十分特殊的事件,要求事件處理函數(shù)內(nèi)部不能阻塞當(dāng)前線程,而window.alert/confirm/prompt/showModalDialog卻恰恰就會阻塞當(dāng)前線程,因此H5規(guī)范中以明確在beforeunload和unload中直接無視這幾個方法的調(diào)用。
Since 25 May 2011, the HTML5 specification states that calls to window.showModalDialog(), window.alert(), window.confirm() and window.prompt() methods may be ignored during this event.(onbeforeunload#Notes)[https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Notes]
在chrome/chromium下會報(bào)"Blocked alert/prompt/confirm() during beforeunload/unload."的JS異常,而firefox下則連異常都懶得報(bào)。
?既然不給用window.confirm,那么如何彈出二次確定對話框呢?其實(shí)beforeunload事件已經(jīng)為我們準(zhǔn)備好了。只要改成
window.onbeforeunload = function(){ var msg = "Do u want to leave? Changes u made may be lost." return msg }
?通過DOM0 Event Model的方式監(jiān)聽beforeunload事件時,只需返回值不為undefined或null,即會彈出二次確定對話框。而IE和Chrome/Chromium則以返回值作為對話框的提示信息,F(xiàn)irefox4開始會忽略返回值僅顯式內(nèi)置的提示信息.
?太不上道了吧,還在用DOM0 Event Model:( 那我們來看看DOM2 Event Model是怎么一個玩法
// Microsoft DOM2-ish Event Model window.attachEvent("onbeforeunload", function(){ var msg = "Do u want to leave? Changes u made may be lost." var evt = window.event evt.returnValue = msg })
對于巨硬獨(dú)有的DOM2 Event Model,我們通過設(shè)置window.event.returnValue為非null或undefined來實(shí)現(xiàn)彈出窗的功能(注意:函數(shù)返回值是無效果的)
那么標(biāo)準(zhǔn)的DOM2 Event Model呢?我記得window.event.returnValue是 for ie only的,但事件處理函數(shù)的返回值又木有效果,那只能想到event.preventDefault()了,但event.preventDefault()沒有帶入?yún)⒌闹剌d,那么是否意味通過標(biāo)準(zhǔn)DOM2 Event Model的方式就不支持自定義提示信息呢?
window.addEventListeners("beforeunload", function(e){ e.preventDefault() })
在FireFox上成功彈出對話框,但Chrome/Chromium上卻啥都沒發(fā)生。。。。。。
坑2: HTMLElement.addEventListener事件綁定?event.preventDefault()這一玩法就FireFox支持,Chrome這次站到IE的隊(duì)列上了。綜合起來的玩法是這樣的
;(function(exports){ exports.genDispose = genDispose /** * @param {Function|String} [fnBody] - executed within the dispose method when it"s data type is Function * as return value of dispose method when it"s data type is String * @param {String} [returnMsg] - as return value of dispose method * @returns {Function} - dispose method */ function genDispose(fnBody, returnMsg){ var args = getArgs(arguments) return function(e){ args.fnBody && args.fnBody() if(e = e || window.event){ args.returnMsg && e.preventDefault && e.preventDefault() e.returnValue = args.returnMsg } return args.returnMsg } } function getArgs(args){ var ret = {fnBody: void 0, returnMsg: args[1]}, typeofArg0 = typeof args[0] if ("string" === typeofArg0){ ret.returnMsg = args[0] } else if ("function" === typeofArg0){ ret.fnBody = args[0] } retrn ret } }(window)) // uses var dispose = genDispose("Do u want to leave? Changes u made may be lost.") window.onbeforeunload = dispose window.attachEvent("onbeforeunload", dispose) window.addEventListener("beforeunload", dispose)坑3: 尊重用戶的選擇
?有辦法阻止用戶關(guān)閉或刷新頁面嗎?沒辦法,二次確定已經(jīng)是對用戶操作的最大限度的干擾了。
問題未解決——Cross-domain Redirection;(function(exports){ exports.Logout = Logout function Logout(url){ if (this instanceof Logout);else return new Logout(url) this.url = url } Logout.prototype.exec = function(){ var xhr = new XMLHttpRequest() xhr.open("GET", this.url, false) xhr.send() } }(window)) var url = "http://pseudo.com/logout", logout = new Logout(url) var dispose = $.proxy(logout.exec, logout) var prefix = "on" (window.attachEvent || (prefix="", window.addEventListener))(prefix + "unload", dispose)
?當(dāng)我以為這樣就能交功課時,卻發(fā)現(xiàn)登出url響應(yīng)狀態(tài)編碼為302,而響應(yīng)頭Location指向另一個域的資源,并且不存在Access-Control-Allow-Origin等CORS響應(yīng)頭信息,而XHR對象不支持Cross-domain Redirection,因此登出失效。
?以前只知道XHR無法執(zhí)行Cross-domain資源的讀操作(支持寫操作),但只以為僅僅是不支持respose body的讀操作而已,沒想到連respose header的讀操作也不支持。那怎么辦呢?既然讀操作不行那采用嵌套Cross-domain資源總行吧。然后有了以下的填坑過程:
第一想到的就是嵌套iframe來實(shí)現(xiàn),當(dāng)iframe的實(shí)例化成本太高了,導(dǎo)致iframe還沒來得及發(fā)送請求就已經(jīng)完成unload過程了;
于是想到了通過script發(fā)起請求, 因?yàn)閞espose body的內(nèi)容不是有效腳本,因此會報(bào)腳本解析異常,若設(shè)置type="text/tpl"等內(nèi)容時還不會發(fā)起網(wǎng)絡(luò)請求;另外iframe、script等html元素均要加入DOM樹后才能發(fā)起網(wǎng)絡(luò)請求;
最后想到HTMLImageElement,只要設(shè)置src屬性則馬上發(fā)起網(wǎng)絡(luò)請求,而且返回非法內(nèi)容導(dǎo)致解析失敗時還是默默忍受,特別適合這次的任務(wù):)
?于是得到下面的版本
;(function(exports){ exports.Logout = Logout function Logout(url){ if (this instanceof Logout);else return new Logout(url) this.url = url } Logout.prototype.exec = function(){ var img = Image ? new Image() : document.createElement("IMG") img.src = this.url } }(window))[before]unload導(dǎo)致性能下降?
?現(xiàn)在我們都明白如何利用[before]unload來做資源釋放等善后工作了。
?但請記住一點(diǎn):由于[before]unload事件會降低頁面性能,因此僅由于需要做重要的善后或不可逆的清理工作時才監(jiān)聽這兩個事件。
?以前,當(dāng)我們從頁面A跳轉(zhuǎn)到頁面B時,頁面A的所有資源將被釋放(銷毀DOM對象,回收J(rèn)S對象, 釋放解碼后的Image資源等);后來各大瀏覽器廠商分別采用bfcache/page cache/fast history navigation機(jī)制,將頁面A的狀態(tài)保存到緩存中,當(dāng)通過瀏覽器的后退/前進(jìn)按鈕跳轉(zhuǎn)時馬上從緩存中恢復(fù)頁面,而不是重新實(shí)例化。以下情況將不被緩存起來:
監(jiān)聽unload或beforeunload事件;
響應(yīng)頭Cache-Control: no-store;
對于采用HTTPS協(xié)議的響應(yīng)頭,滿足以下一個或以上:
3.1. Cache-Control: no-cache
3.2. Pragma: no-cache
3.3. 存在Expires超期的
發(fā)生跳轉(zhuǎn)時,頁面存在未加載完的資源
旗下iframe存在上述情況的
頁面在iframe中渲染,當(dāng)用戶修改iframe.src加載其他文檔到該iframe時
?因此若執(zhí)行不可逆的清理工作時,對于現(xiàn)代瀏覽器而言我們應(yīng)該訂閱pagehide事件,而不是unload事件,以便利用Page Cache機(jī)制。
事件發(fā)生順序:load->pageshow->pagehide->unload
pageshow和pagehide的事件對象存在一個persisted屬性,為true時表示從cache中恢復(fù),false表示重新實(shí)例化。
?經(jīng)簡單測試發(fā)現(xiàn)chrome默認(rèn)沒有啟用該特性,而Firefox則默認(rèn)啟用。實(shí)驗(yàn)代碼:
// index.html window.addEventListener("load", function(){ console.log("index.load") window.test = true }) window.addEventListener("pageshow", function(e){ console.log("index.pageshow.persisted:" + e.persisted) console.log("index.test:" + window.test) }) next.html
// next.html window.addEventListener("load", function(){ console.log("next.load") }) window.addEventListener("pageshow", function(e){ console.log("next.pageshow.persisted:" + e.persisted) })
運(yùn)行環(huán)境:FireFox
操作步驟:1.首先訪問index.html,2.然后點(diǎn)擊鏈接跳轉(zhuǎn)到next.html,3.然后點(diǎn)擊瀏覽器的回退按鈕跳轉(zhuǎn)到index.html,4.最后點(diǎn)擊瀏覽器的前進(jìn)按鈕跳轉(zhuǎn)到next.html。
輸出結(jié)果:
// 1 index.load index.pageshow.persisted:false index.test:true // 2 next.load next.pageshow.persisted:false // 3 index.pageshow.persisted:true index.test:true //4 next.pageshow.persisted:true
?看到頁面是從bfcache恢復(fù)而來的,所以JS對象均未回收,因此window.test值依然有效。另外load僅在頁面初始化后才會觸發(fā),因此從bfcache中恢復(fù)頁面時并不會觸發(fā)。
?假如在index.html上訂閱了unload或beforeunload事件,那么該頁面將不會保存到bfcache。
?另外通過jQuery.ready來監(jiān)聽頁面初始化事件時,不用考慮bfcache的影響,因?yàn)樗鼛臀覀兲幚砗昧?)
若有紕漏望請指正,謝謝!
尊重原創(chuàng),轉(zhuǎn)載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/5647649.html 肥子John^_^
window-onbeforeunload-not-working
beforeunload
unload
prompt-to-unload-a-document
webkit page cache i - the basics
webkit page cache ii - the unload event
pagehide
pageshow
Redirects Do’s and Don’ts
cross-browser-onload-event-and-the-back-button
Using_Firefox_1.5_c aching#New_browser_events
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79905.html
摘要:前言是否曾經(jīng)被業(yè)務(wù)提出能改改這個單選框的顏色吧讓它和主題顏色搭配一下吧,然后苦于原生不支持換顏色,最后被迫自己手?jǐn)]一個湊合使用。設(shè)置為的樣式行為特征單選框的行為特征,明顯就是選中與否,及選中狀態(tài)的改變事件,因此我們必須保持對外提供事件。 前言 ?是否曾經(jīng)被業(yè)務(wù)提出能改改這個單選框的顏色吧!讓它和主題顏色搭配一下吧!,然后苦于原生不支持換顏色,最后被迫自己手?jǐn)]一個湊合使用。若拋開inpu...
摘要:前言繼上篇魔法堂稍稍深入偽類選擇器記錄完偽類后,我自然而然要向偽元素伸出魔掌的啦。和的注意事項(xiàng)默認(rèn)必須設(shè)置屬性,否則一切都是無用功默認(rèn),就是和的內(nèi)容無法被用戶選中的偽元素和偽類結(jié)合使用形如。 前言 ?繼上篇《CSS魔法堂:稍稍深入偽類選擇器》記錄完偽類后,我自然而然要向偽元素伸出魔掌的啦^_^。本文講講述偽元素以及功能強(qiáng)大的Contet屬性,讓我們可以通過偽元素更好地實(shí)現(xiàn)更多的可能! ...
摘要:前言過去零零星星地了解和使用和等偽類偽元素選擇器,最近看書時發(fā)現(xiàn)這方面有所欠缺,于是決定稍微深入學(xué)習(xí)一下,以下為偽類部分的整理。偽類偽類選擇器實(shí)質(zhì)上是讓設(shè)計(jì)師可以根據(jù)元素特定的狀態(tài),設(shè)置不同的視覺效果。也就是符合以下選擇器的元素均支持狀態(tài)。 前言 ?過去零零星星地了解和使用:link、::after和content等偽類、偽元素選擇器,最近看書時發(fā)現(xiàn)這方面有所欠缺,于是決定稍微深入學(xué)習(xí)...
摘要:不耽誤表單提交數(shù)據(jù)雖然我們無法看到的元素,但當(dāng)表單提交時依然會將隱藏的元素的值提交上去。讓元素在見面上不可視,但保留元素原來占有的位置。不過由于各瀏覽器實(shí)現(xiàn)效果均有出入,因此一般不會使用這個值。繼承父元素的值。 前言 ?還記得面試時被問起請說說display:none和visibility:hidden的區(qū)別嗎?是不是回答完display:none不占用原來的位置,而visibilit...
摘要:五的子類對象會返回一個集合對象,集合內(nèi)存儲類型的元素。七的子類初看很有可能以為集合元素就是單選表單元素,其實(shí)可以存儲任意類型的表單元素。八的子類開始,將返回子類的對象,其行為特征和一致。但在前,我們應(yīng)該先了解清楚的類型的特征。 一、前言 大家先看看下面的js,猜猜結(jié)果會怎樣吧! 可選答案: ①. 獲取id屬性值為id的節(jié)點(diǎn)元素 ②...
閱讀 2942·2023-04-26 01:32
閱讀 1540·2021-09-13 10:37
閱讀 2278·2019-08-30 15:56
閱讀 1669·2019-08-30 14:00
閱讀 3042·2019-08-30 12:44
閱讀 1961·2019-08-26 12:20
閱讀 1056·2019-08-23 16:29
閱讀 3227·2019-08-23 14:44