解決瀏覽器返回頁面不重新整理的問題

Sakura同志發表於2019-04-14

現象

由於 IOS 系統的頁面快取機制,經常會遇到在移動端返回到上一個頁面不重新整理的情況。

比如今天在開發微信 H5 頁面的時候,在IOS微信內建瀏覽器中返回上一頁時,上一個頁面不會被重新整理。 而通常在瀏覽器快取機制中,在返回上一頁的操作中, html/css/js/介面 等動靜態資源不會重新請求,但是js會重新載入。但在IOS微信頁面中js也會儲存上一頁面最後執行的狀態,不會重新執行js。 使用這種模式的快取機制可以加快渲染速度,但是部分資料需要經常展示和編輯的情況下會導致不同步。比如‘詳情頁’跳轉到‘編輯頁’,編輯完後再返回到‘詳情頁’,如果‘詳情頁’資料展示未進行同步修改那肯定是不能接受的。 在webview和5+的混合app模式中,也會遇到這種返回上一個頁面不重新整理的問題

產生原因

瀏覽器前進/後退快取

這裡提到一個概念,瀏覽器前進/後退快取(Backward/Forward Cache, BF Cache),當然也有人叫 disk Cache。 BF Cache 是一種瀏覽器優化, HTML 標準並未指定其如何進行快取,因此快取行為是各瀏覽器各自實現,所以不盡相同。 由於不是 HTTP 快取,所以通過標頭檔案快取設定 no-cache 是無效的。當然也不能以 HTTP 快取機制來理解 BF Cache。

解決思路

設定瀏覽歷史當前記錄

//監聽後退返回事件 --解決微信返回不重新整理問題
pushHistory: function(){
    window.addEventListener("popstate", function(e) {
        self.location.reload();
    }, false);
    var state = {
        title : "",
        url : "#"
    };
    window.history.replaceState(state, "", "#");
},

/**
* 頁面初始化呼叫pushHistory,監聽popstate事件和執行replaceState()
* 當執行replaceState()時,不會觸發popstate事件,所以不會重複重新整理
* 當在ios微信內建瀏覽器中執行瀏覽器前進後退操作時,觸發popstate事件,執行location.reload()
* 但是在谷歌瀏覽器中執行瀏覽器前進後退操作時,不會觸發popstate事件!因為不在一個document中了
* 但是如果手動改變URL的雜湊值,比如www.baidu.com# 改成 www.baidu.com#1 會觸發popstate事件,執行location.reload()
* 以上如果有一項不理解或不清晰,請往下看原理深究,你會找到答案
**/
複製程式碼

原理深究

前端路由實現(history)原理

以前瀏覽器操作瀏覽器歷史記錄主要依據history物件。在它的 proto 繼承有 back、forward、go 等函式。 而 HTML5 後新增 popState 來控制瀏覽歷史記錄的 api。有可以儲存當前歷史記錄點的 pushState、替換當前歷史記錄點的 replaceState、和監聽歷史記錄點的 onPopState。

window.history.pushState(state, title, url)

  • state (狀態物件): 一個js物件,可以用來儲存一些簡單的資料。值得一提的是如果被啟用的歷史記錄條目是通過 pushState 或 replaceState 呼叫而生成的,popState事件的state屬性包含歷史條目的狀態物件的副本。可以從 history.state中讀取儲存的 state。
  • title (標題): 目前被瀏覽器忽略了,所以通常傳入一個空字串
  • URL (地址): 新的歷史記錄條目的地址。值得一提的是呼叫 pushState() 後瀏覽器並不會立即載入這個 URL,但可能會在使用者重新開啟瀏覽器時等某些情況載入這個 URL。並且新 URL 必須與當前 URL 同源,否則會丟擲一個異常。 新 URL 可為絕對路徑或者是相對路徑。

window.history.replaceState(state, title, url)

  • 相同之處是兩個API都會操作瀏覽器的歷史記錄,而不會引起頁面的重新整理,也不會去驗證這個新條目對應的網頁是否存在。
  • 不同之處在於 pushState 會增加一條新的歷史記錄,而 replaceState 則會替換當前的歷史記錄。
  • 兩個 API 都絕對不會觸發 hashchange 事件,即使新的 URL 與舊的 URL 僅雜湊不同也是如此
  • 如果傳遞了 stateObj,就會更新當前條目關聯的狀態物件;如果傳遞了 url,就會替換當前條目的頁面地址和更改瀏覽器位址列的地址。

window.onpopstate事件

  • 僅在瀏覽器前進後退操作、history.go/back/forward 呼叫、hashChange 的時候觸發
  • history.pushState 和 history.replaceState 都不會觸發這個事件
  • firfox,chrome 在頁面首次開啟時都不會觸發 popstate 事件,但是 safari 會
  • popstate 事件作用範圍僅在於一個 document 裡面,由於 pushState 和 hashchange 都不會改變網頁的內容也就是 document,所以這樣的網頁裡面才能有效使用 popstate。假如我們輸入一個網頁,並且在它裡面新增了 popstate 回撥;然後通過連結跳轉的方式轉到另外一個網頁;再點選後退按鈕回到第一個網頁。這樣的情況,第一個網頁裡面的 popstate 回撥,除了有可能因為頁面初始化被觸發外,瀏覽器的後退前進是不會觸發它的,因為這種方式改變了視窗的 document。但是!!!在 iOS 微信內建瀏覽器上,重複上述的操作,會觸發 onpopstate 事件!,所以我們才能解決在微信瀏覽器上頁面返回不重新整理的問題

pushState 和replaceState 的第一個引數 stateObj,會與第三個引數對應的歷史條目繫結在一塊,當 popstate 事件觸發的時候,意味著有新的歷史記錄條目被啟用,在 popstate 的事件物件裡面,有一個 state 屬性,會返回這個啟用條目關聯的 stateObj 物件的拷貝。一個歷史記錄條目只有當它是被 pushState 建立的,或者用 replaceState 改過的,才可能有關聯的 stateObj 物件,所以當某些非這2種條件的歷史記錄條目被啟用的時候,可能拿到的 stateObj 就是 null。

禁止返回上一頁的一種方案

/**
* 向歷史記錄中手動新增一條記錄
* 使用者選擇返回的時候,每次都會消耗一個 history 實體,此時觸發 popstate 監聽事件,再手動新增一條history實體記錄
* 所以使用者無論點選多少次都會永遠留在這個頁面了,當然頁面也不會重新整理
**/
function pushHistory() {
    window.history.pushState(null, null, "#");
    window.addEventListener("popstate", function (e) {
        console.log(e);
        window.history.pushState(null, null, "#");
    }, false);
}
複製程式碼

URL中的#

URL 中的 # 就表示的是 URL 的雜湊值

  • #代表網頁中的一個位置,其右邊的字元,就是該位置的識別符號。
設定方法:
step1:設定一個錨點<a href="#print">定位到print位置</a>
step2:在頁面需要定位的內容加上id="print"。例如:<div id="print"></div> 或者 <a name="print"></a>
測試:step1設定的錨點,step2中id為print的內容會滾動到頁面頂端(可觀察滾動條的距離)。同時,頁面的url末端中會出現 # print的雜湊值。
複製程式碼
  • HTTP請求不包含#
    #號是用來指導瀏覽器動作的,對伺服器端完全無用。
    在第一個#後面出現的任何字元,都會被瀏覽器解讀為位置識別符號。這意味著,這些字元都不會被髮送到伺服器端。
    訪問下面的網址: www.w3cschool.cn/#hello 瀏覽器實際發出的請求時這樣的:

    解決瀏覽器返回頁面不重新整理的問題

  • 改變#不觸發網頁過載
    單單改變#後的內容,瀏覽器只會滾動到相應位置,不會重新載入網頁。
    瀏覽器也不會重新向伺服器請求頁面

  • 改變#會改變瀏覽器的訪問歷史
    每一次改變#後的部分,都會在瀏覽器的訪問歷史中增加一個記錄,使用"後退"按鈕,就可以回到上一個位置。

  • window.location.hash讀取#值
    window.location.hash這個屬性可讀可寫。讀取時,可以用來判斷網頁狀態是否改變;寫入時,則會在不過載網頁的前提下,創造一條訪問歷史記錄。

  • onhashchange事件
    這是一個HTML 5新增的事件,當#值發生變化時,就會觸發這個事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支援該事件。

    // 它的使用方法有三種:
    window.onhashchange = func;
    <body onhashchange="func();">
    window.addEventListener("hashchange", func, false);
    複製程式碼
  • Google抓取#的機制
    預設情況下,Google的網路蜘蛛忽視URL的#部分。
    但是,Google還規定,如果你希望Ajax生成的內容被瀏覽引擎讀取,那麼URL中可以使用"#!",Google會自動將其後面的內容轉成查詢字串_escaped_fragment_的值。
    比如,Google發現新版twitter的URL:twitter.com/#!/username
    就會自動抓取另一個URL:twitter.com/?escaped_fragment=/username
    通過這種機制,Google就可以索引動態的Ajax內容。

相關文章