成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Ajax局部頁面刷新和History API結(jié)合的陷阱

JasinYip / 1475人閱讀

摘要:對于那些老網(wǎng)站或者老項目來說全盤改造成并不現(xiàn)實,于是就有了局部頁面刷新這個解決方案。如果不知道局部頁面刷新是何物請看這里,這里和這里。但實際上,第一次后退無法還原的內(nèi)容陷阱,第二次后退頁面刷新了一切恢復(fù)最初的樣子。

ajax在現(xiàn)代網(wǎng)站已經(jīng)得到非常普遍地應(yīng)用,主要的好處大家都知道(異步加載數(shù)據(jù),不用刷新整個瀏覽器,更小的數(shù)據(jù)傳輸尺寸)。對于那些老網(wǎng)站或者老項目來說全盤改造成ajax并不現(xiàn)實,于是就有了“局部頁面刷新”這個解決方案。如果不知道“局部頁面刷新”是何物請看這里,這里和這里。

在我們的項目里,將原來的iframe或者frame統(tǒng)統(tǒng)替換成了時髦的div,然后修改了頁面上所有發(fā)起請求的地方,把響應(yīng)內(nèi)容jQuery.loaddiv里。

于是乎原來老舊的網(wǎng)站變成了一個時髦的基于ajax的網(wǎng)站,每個頁面?zhèn)鬏數(shù)臄?shù)據(jù)量變小了,再也不用解決令人頭疼的:

為了消除滾動條讓iframe自適應(yīng)大小

如何訪問parent window變量的問題(還有如何訪問parent的parent的parent... window變量的問題)

如何訪問child iframe里的變量[]的問題了(還有如何訪問child的child的child... iframe里的變量的問題)

因為大家永遠(yuǎn)都在同一個window里,而且div本身就會根據(jù)內(nèi)容自動撐大。但是等等!瀏覽器怎么不能后退了?

我們的那個項目是一個滿大街可見的XX管理信息系統(tǒng),這種系統(tǒng)最常見的布局就是左側(cè)一個樹形菜單區(qū)域,右側(cè)是一個功能區(qū)域,功能區(qū)域里有一個查詢條件區(qū)域(里面有個查詢按鈕),還有一個空白的區(qū)域用來顯示查詢結(jié)果,同時是用戶操作數(shù)據(jù)的地方(比如form表單)。

在iframe時代,上面講到的4個區(qū)域都是一個iframe,這也就意味著我們可以有很{{BANNED}}的后退能力。
當(dāng)然了一般來說用戶最常用的就是對操作區(qū)域做后退動作,比如查詢一下,選擇一條記錄點(diǎn)擊修改,看到form表單,修改一下,在點(diǎn)擊保存前后悔了,點(diǎn)擊瀏覽器的后退,回到查詢結(jié)果頁面。

但是在引入了ajax后無法后退了,因為ajax請求不會記錄到瀏覽器歷史里,歷史都沒有了自然就無法后退了。

好在Html5的History API能夠幫助我們解決問題。我們可以人為的使用history.pushState來人造歷史信息,并且通過監(jiān)聽popstate事件來知道用戶點(diǎn)擊了瀏覽器后退或前進(jìn)按鈕,然后將頁面元素還原到歷史上的某個狀態(tài)。關(guān)于Html5 History API的相關(guān)信息可以看這里。

但是事情遠(yuǎn)不止這么簡單,下面是我們遇到的一些坑:

陷阱1:重復(fù)執(zhí)行js腳本
// 點(diǎn)擊查詢按鈕的時候人為構(gòu)造一個瀏覽器歷史
$("#some-button").click(function() {
  $(targetSelector).load(url);

  history.pushState({
    container : targetSelector,
    content   : $(targetSelector).html()
  }, null, url);

});

// 當(dāng)瀏覽器后退后者前進(jìn)的時候,我們把當(dāng)時的結(jié)果重新加載到container里來
window.addEventListener("popstate", function() {
  var state = history.state
  $(state.container).html(state.content);
})

一切看上去都OK,直到...我們發(fā)現(xiàn)局部頁面刷新所獲得的結(jié)果里包含了操作dom元素的js。

當(dāng)遇到這種情況時會發(fā)生很奇妙的現(xiàn)象,history state.content是已經(jīng)加載完畢+js執(zhí)行后的結(jié)果,當(dāng)我們重新還原的時候,我們會把這個結(jié)果加載出來,并且又會執(zhí)行一遍js。如果這個js是一個添加dom的動作那么在后退的時候你會看到這個重復(fù)的dom元素。

我們想過跟蹤哪些dom元素是被js修改過的來避免這個問題,但是...這是不現(xiàn)實的。

陷阱2:無法還原到最初狀態(tài)

前面的方案因為load的內(nèi)容里可能有js腳本所以有嚴(yán)重缺陷,于是我們換了個思路,history里保存responseText,而不是已經(jīng)load好后的東西。

// 點(diǎn)擊查詢按鈕的時候人為構(gòu)造一個瀏覽器歷史
$("#some-button").click(function() {
  $(targetSelector).load(url, function(responseText) {
    history.pushState({
      container : targetSelector,
      content   : responseText
    }, null, url);
  });
});

// popstate事件的處理方式一樣

但是仍然遇到了這么一個問題,如果container(刷新目標(biāo)區(qū)域,某個div)原來是有內(nèi)容的,而這個內(nèi)容不是通過ajax局部頁面刷新而來,而是用戶一進(jìn)入這個頁面就已經(jīng)有的,比如使用服務(wù)器端的模板引擎生成的頁面,那么在它加載完html片段后就無法回退了。因為它的內(nèi)容一開始就不在history里(事實上瀏覽器自己產(chǎn)生的history是沒有state的),這樣就形成了退無可退的局面。

如果你想,我們只要保存這個container原來的內(nèi)容不就行了,當(dāng)后退的時候我們直接恢復(fù)它原來的內(nèi)容,但是請看陷阱1

不過當(dāng)發(fā)生退無可退的情況時,我們認(rèn)為已經(jīng)退回到了第一次進(jìn)入頁面的狀態(tài),這個時候我們刷新整個頁面就行了。

陷阱3:多個并列的container

陷阱2的解決方案實際上是基于container之間是屬于嵌套關(guān)系或者就一個container的情況的。如果是這種情況就不行了:

有A和B兩個container,點(diǎn)擊某個按鈕刷新了A的內(nèi)容(產(chǎn)生歷史),然后在點(diǎn)擊某個按鈕刷新的B的按鈕(產(chǎn)生歷史),按照用戶的預(yù)想情況,第一次后退還原B原來的內(nèi)容,第二次后退還原A原來的內(nèi)容。但實際上,第一次后退無法還原B的內(nèi)容(陷阱2),第二次后退頁面刷新了(一切恢復(fù)最初的樣子)。

如果B是嵌套在A里的就無所謂了,第一次后退的時候獲得的是A的state,根據(jù)A的state還原A的內(nèi)容的時候順便把B也還原了,第二次后退頁面刷新,把A也還原了。

而且根據(jù)陷阱1所講,我們也不能在history里存儲A或者B里原來的內(nèi)容。

解決辦法:對于這種操作就不要記錄歷史了。

陷阱4:看到過時頁面

我們在History state里存的是當(dāng)時load時的responseText,當(dāng)我們后退的時候看到的是過時的頁面,比如我們原先查詢結(jié)果里看到有A記錄,然后我們跳轉(zhuǎn)到其他頁面里,然后再后退到查詢結(jié)果頁面看到A記錄還在,但是這個A記錄很可能只是一個幽靈,在數(shù)據(jù)庫里早就已經(jīng)不存在了。如果我們這個時候再對A記錄操作就有出現(xiàn)錯誤。

解決辦法是我們在history state里保存url已經(jīng)相關(guān)的參數(shù),當(dāng)popstate的時候重新發(fā)起請求就行了,這樣一來的話也減少了history存儲state所需要的空間。

// 這里只給get請求的例子,post的原理也差不多
$("#some-button").click(function() {
  $(targetSelector).load(url, function(responseText) {
    history.pushState({
      container : targetSelector,
      url       : url
    }, null, url);
  });
});

window.addEventListener("popstate", function() {
  var state = history.state;
  $(state.container).load(state.url);
});
陷阱5:redirect

即使我們在history state保存了url你就以為沒事了?too simple, too naive!如果我們對這個url發(fā)起的請求被服務(wù)器redirect到另一個url,那么在history state里保存這個url就不對了。

如果我們這個url是用來刪除某條記錄的,服務(wù)器收到請求在數(shù)據(jù)庫里刪除了這條記錄,然后redirect到了首頁url,那么這個時候你在history里應(yīng)該存那個url呢?顯然是首頁的url,因為如果你存了刪除url,那么在后退的時候,我們會重新發(fā)起這個url,想想這多嚇人。

解決辦法其實不太簡單,因為ajax是否被redirect你是不知道的,用jQuery封裝的jqXHR對象也沒法知道這個。

也許鏈WHATWG的XmlHttpRequest.responseURL可以救你,但是瀏覽器兼容性不好。

我的做法在服務(wù)器sendRedirect之前在requestUrlqueryString里添加一個flag,用一個專門的servlet filter判斷過來的請求是否有這個flag,如果有那么就將本次請求的url(也就是redirect到的url)放到response的一個特定的header里。然后就可以用jqXHR.getResponseHeader("some-header")來獲得這個url,把這個url放到history state里。

陷阱6:無法精確還原dom對象的狀態(tài)

不論是保存responseText還是保存url請求參數(shù),都無法在瀏覽器后退的時候精確還原dom對象的狀態(tài),比如我在IE6里有個這樣的特性,你在某個頁面勾選了某個checkbox,然后跳轉(zhuǎn)到一個新的頁面然后再后退,那個checkbox還是處于勾選狀態(tài),這個在利用ajax局部頁面刷新里是完全做不到的,想到用戶和我說以前后退的時候那個勾還在現(xiàn)在勾沒有了,不解決這個BUG就不驗收的事情時才想到iframe的好啊。

所以如果要精確還原dom對象的狀態(tài),得在history.pushState的時候自行把相關(guān)信息保存下來,在popstate的時候用到這些信息并還原dom。

事實上即使用了iframe也并不是所有的瀏覽器會還原dom對象狀態(tài),看這篇文章。

總結(jié)

不要輕易從iframe切換到ajax局部頁面刷新

要自己控制那些ajax局部頁面刷新紀(jì)錄歷史,哪些不記錄,有些時候可能還需要replaceState,不要想當(dāng)然的把所有請求都記錄歷史

把代碼改造成ajax局部頁面刷新只是第一步,還需要對整個網(wǎng)站、應(yīng)用的UI做規(guī)劃和設(shè)計,關(guān)于這個問題不存在通用的解決方案

參考資料

MANIPULATING HISTORY FOR FUN & PROFIT

Session history and navigation

Manipulating the browser history

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80052.html

相關(guān)文章

  • HTML5 history API,創(chuàng)造更好瀏覽體驗

    摘要:而唯一不引發(fā)刷新的參數(shù)并不會發(fā)送到服務(wù)器,因此服務(wù)器無法獲得狀態(tài)。目前建議設(shè)置為空字符串。此外請注意,及本身調(diào)用時是不觸發(fā)事件的。我認(rèn)為,按照漸進(jìn)增強(qiáng)的思路,這樣就是最好的了,也就是只使用較少的代碼優(yōu)化高級瀏覽器的使用體驗。 HTML5 history API有什么用呢? 從Ajax翻頁的問題說起 請想象你正在看一個視頻下面的評論,在翻到十幾頁的時候,你發(fā)現(xiàn)一個寫得稍長,但非常有趣的評...

    zgbgx 評論0 收藏0
  • pjax不再神秘,hash、state那點(diǎn)事

    摘要:初步理解如果最近打電話給武漢的小伙伴,他說信號不好,那么相信我,他肯定不是真的信號不好,也不是不想和你說話,而是他可能在冰箱里。。。 初步理解 如果最近打電話給武漢的小伙伴,他說信號不好,那么相信我,他肯定不是真的信號不好,也不是不想和你說話,而是他可能在冰箱里。。。武漢的天氣從來都是喜怒無常的,是吧,屌絲氣十足,今年也是絲毫看不出有任何逆襲的跡象和可能性,當(dāng)然咱也沒必要去操那個心;好...

    solocoder 評論0 收藏0
  • SPA那點(diǎn)事

    摘要:單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。單頁面應(yīng)用的優(yōu)勢無刷新體驗沒有了令人詬病的頁面頻繁刷新,同時節(jié)約瀏覽器資源,路由響應(yīng)比較及時,提升了用戶的體驗。 前端猿一天不學(xué)習(xí)就沒飯吃了,后端猿三天不學(xué)習(xí)仍舊有白米飯擺于桌前。IT行業(yè)的快速發(fā)展一直在推動著前端技術(shù)棧在不斷地更新?lián)Q代,前端的發(fā)展成了互聯(lián)網(wǎng)時代的一個縮影。而單頁面應(yīng)用的發(fā)展...

    PumpkinDylan 評論0 收藏0
  • SPA那點(diǎn)事

    摘要:單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。單頁面應(yīng)用的優(yōu)勢無刷新體驗沒有了令人詬病的頁面頻繁刷新,同時節(jié)約瀏覽器資源,路由響應(yīng)比較及時,提升了用戶的體驗。 前端猿一天不學(xué)習(xí)就沒飯吃了,后端猿三天不學(xué)習(xí)仍舊有白米飯擺于桌前。IT行業(yè)的快速發(fā)展一直在推動著前端技術(shù)棧在不斷地更新?lián)Q代,前端的發(fā)展成了互聯(lián)網(wǎng)時代的一個縮影。而單頁面應(yīng)用的發(fā)展...

    Lsnsh 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<