<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          瀏覽器的前進(jìn)后退時,頁面的緩存機(jī)制

          共 7816字,需瀏覽 16分鐘

           ·

          2021-12-10 02:54


          后退/前進(jìn)緩存(Back/forward cache, 以下簡稱bfcache)是一種瀏覽器優(yōu)化,可實現(xiàn)即時的后退和前進(jìn)導(dǎo)航。它顯著改善了用戶的瀏覽體驗,尤其是那些網(wǎng)絡(luò)或設(shè)備速度較慢的用戶。

          作為web開發(fā)人員,了解如何在所有瀏覽器上基于bfcache優(yōu)化頁面非常重要。這樣可以提高用戶體驗。

          瀏覽器兼容性

          Firefox和Safari都早已支持bfcache,包括桌面和移動設(shè)備。

          從86版開始,Chrome已經(jīng)為一小部分用戶啟用了Android上的跨站點導(dǎo)航。在chrome87中,bfcache支持將推廣到所有Android用戶進(jìn)行跨站點導(dǎo)航,目的是在不久的將來也支持相同的站點導(dǎo)航。

          bfcache基礎(chǔ)知識

          bfcache是一個內(nèi)存中的緩存,它在用戶離開時存儲頁面的完整快照(包括JavaScript堆)。由于整個頁面都在內(nèi)存中,如果用戶決定返回,瀏覽器可以快速輕松地恢復(fù)頁面。

          有多少次你訪問一個網(wǎng)站,點擊一個鏈接進(jìn)入另一個頁面,卻發(fā)現(xiàn)這不是你想要的,然后點擊后退按鈕?此時,bfcache對上一頁的加載速度會有很大的影響:

          不支持bfc時:將啟動一個新的請求來加載上一個頁面,并且,根據(jù)該頁面針對重復(fù)訪問的優(yōu)化程度,瀏覽器可能需要重新下載、重新解析和重新執(zhí)行剛下載的部分(或全部)資源。

          開啟了bfc時:加載上一個頁面基本上是即時的,因為整個頁面可以從內(nèi)存中恢復(fù),而不必訪問網(wǎng)絡(luò)。

          bfcache不僅加快了導(dǎo)航速度,還減少了數(shù)據(jù)使用,因為不必再次下載資源。

          Chrome的使用數(shù)據(jù)顯示,桌面上十分之一的導(dǎo)航和手機(jī)上五分之一的導(dǎo)航要么后退要么前進(jìn)。啟用bfcache后,瀏覽器可以消除每天數(shù)十億個網(wǎng)頁的數(shù)據(jù)傳輸和加載時間!

          cache是如何工作的

          bfcache使用的“緩存”不同于HTTP緩存(這在加速重復(fù)導(dǎo)航方面也很有用)。bfcache是內(nèi)存中整個頁面的快照(包括JavaScript堆),而HTTP緩存只包含以前發(fā)出的請求的響應(yīng)。由于加載頁面所需的所有請求都能從HTTP緩存中得到滿足的情況非常罕見,因此使用bfcache恢復(fù)進(jìn)行的重復(fù)訪問總是比最優(yōu)化的非bfcache導(dǎo)航更快。

          然而,在內(nèi)存中創(chuàng)建頁面快照是有一定復(fù)雜性的,特別是涉及到如何最好地保存正在進(jìn)行的代碼。例如,當(dāng)頁面在bfcache中時,如何處理到達(dá)超時的setTimeout()調(diào)用?

          答案是,瀏覽器暫停運(yùn)行任何掛起的計時器或未resolved的Promise(實際上是JavaScript任務(wù)隊列中所有掛起的任務(wù)),并在頁面從bfcache恢復(fù)時(或如果)恢復(fù)處理任務(wù)。

          在某些情況下,暫停任務(wù)是低風(fēng)險的(例如,超時或Promise),但在其他情況下,它可能會導(dǎo)致非常混亂或意外的行為。例如,如果瀏覽器暫停IndexedDB事務(wù)中所需的任務(wù),它可能會影響同一源中打開的其他選項卡(因為多個選項卡可以同時訪問同一個IndexedDB數(shù)據(jù)庫)。因此,瀏覽器通常不會嘗試在IndexedDB事務(wù)中間緩存頁面,也不會使用可能影響其他頁面的api。

          有關(guān)各種API用法如何影響頁面的bfcache的詳細(xì)信息,請參考下文的內(nèi)容。

          監(jiān)聽bfcache的API

          雖然bfcache是瀏覽器自動進(jìn)行的一種優(yōu)化,但對于開發(fā)人員來說,知道何時發(fā)生這種情況仍然很重要,這樣他們就可以針對bfcache優(yōu)化自己的頁面,并相應(yīng)地調(diào)整任何指標(biāo)或性能度量。

          用于觀察bfcache的主要事件是頁面轉(zhuǎn)換事件pageshow和pagehide,這兩個事件存在的時間和bfcache存在的時間一樣長,并且在當(dāng)今使用的幾乎所有瀏覽器中都受支持。

          新的頁面生命周期事件?freeze?和?resume?也會在頁面進(jìn)入或離開bfcache時以及在其他一些情況下觸發(fā)。例如,當(dāng)后臺選項卡凍結(jié)以最小化CPU使用率時。注意,頁面生命周期事件目前僅在基于Chromium的瀏覽器中受支持。

          監(jiān)聽頁面從bfc中恢復(fù)

          當(dāng)頁面最初加載時,pageshow事件在load事件之后立即激發(fā)。另外,頁面從bfcache還原時,pageshow也會觸發(fā)。pageshow事件有一個persisted屬性,如果從bfcache還原頁面,則該屬性為true;如果不是,則為false。您可以使用persisted屬性來區(qū)分常規(guī)頁面加載和bfcache還原。例如:

          window.addEventListener('pageshow', function(event) {  if (event.persisted === true) {    // 頁面從bfc中恢復(fù)    console.log('This page was restored from the bfcache.');  } else {    // 頁面正常加載    console.log('This page was loaded normally.');  }});

          在支持頁面生命周期API的瀏覽器中,當(dāng)頁面從bfcache還原時(就在pageshow事件之前),resume事件也會觸發(fā),不過當(dāng)用戶重新訪問凍結(jié)的背景選項卡時,它也會觸發(fā)。如果要在凍結(jié)頁面(包括bfcache中的頁面)后恢復(fù)頁面狀態(tài),可以使用freeze事件,但如果要測量站點的bfcache命中率,則需要使用pageshow事件。在某些情況下,您可能需要同時使用這兩種方法。

          監(jiān)聽頁面進(jìn)入bfc

          pagehide事件是pageshow事件的對應(yīng)項。當(dāng)頁面正常加載或從bfcache還原時,將激發(fā)pageshow事件。pagehide事件在頁面正常卸載或瀏覽器試圖將其放入bfcache時觸發(fā)。

          pagehide事件還有一個persistent屬性,如果它是false,那么您可以確信頁面不會進(jìn)入bfcache。但是,如果persistent屬性為true,則不能保證將緩存頁。這意味著瀏覽器打算緩存頁面,但可能有一些因素導(dǎo)致無法緩存。

          window.addEventListener('pagehide', function(event) {  if (event.persisted === true) {   // 頁面可能會進(jìn)入bfc緩存   console.log('This page *might* be entering the bfcache.');  } else {    // 頁面會正常退出,并且會被丟棄    console.log('This page will unload normally and be discarded.');  }});

          類似地,freeze事件將在pagehide事件之后立即觸發(fā)(如果事件的persistent屬性為true),但這同樣意味著瀏覽器打算緩存頁面。在下面描述的情況下,它可能仍然必須丟棄它。

          為bfcache優(yōu)化頁面

          并不是所有的頁面都存儲在bfcache中,即使頁面確實存儲在那里,它也不會無限期地停留在那里。開發(fā)人員必須了解是什么使頁面符合bfcache的條件(和不符合條件),以最大限度地提高緩存命中率。

          下面幾節(jié)概括了使瀏覽器盡可能緩存頁面的最佳實踐。

          不要使用 unload 事件

          在所有瀏覽器中優(yōu)化bfcache的最重要方法是永遠(yuǎn)不要使用unload事件。

          unload事件對于瀏覽器來說是有問題的,因為它早于bfcache觸發(fā),并且網(wǎng)絡(luò)上的許多頁面都是在一個(合理的)假設(shè)下運(yùn)行的:unload事件觸發(fā)后,頁面將不再存在了。這就帶來了一個挑戰(zhàn),因為許多頁面的構(gòu)建都是基于這樣一個假設(shè):unload事件將在用戶離開時觸發(fā)。然而,事實已經(jīng)不是這樣了(而且在很長一段時間內(nèi)都不是這樣)。

          譯者注:這里我的理解是,瀏覽器在設(shè)計unload事件之初,就是在頁面不需要的時候觸發(fā)。如果開發(fā)者監(jiān)聽了unload事件,則表示頁面銷毀時需要執(zhí)行一些邏輯,這個時候,頁面自然是不需要再進(jìn)行緩存了。然而這種情況下,很多開發(fā)者是希望頁面被緩存的,這和unload事件本身的含義有沖突。

          所以瀏覽器面臨著一個兩難的選擇,他們必須在能改善用戶體驗的同時也可能有破壞頁面的風(fēng)險。

          Firefox選擇了如果添加unload偵聽器,那么頁面就不符合bfcache的條件,這樣做風(fēng)險較小,但也會使很多頁面無法bfc。Safari會嘗試緩存一些監(jiān)聽了unload事件的頁面,但是為了減少潛在的破壞,當(dāng)用戶導(dǎo)航離開時,Safari不會觸發(fā)unload事件。

          由于Chrome中65%的頁面都注冊了unload事件偵聽器,為了能夠緩存盡可能多的頁面,Chrome選擇與Safari保持一致。

          不要使用unload事件,使用pagehide事件。pagehide事件在unload事件觸發(fā)的所有情況下都會觸發(fā),并且在頁面放入bfcache時也會觸發(fā)。

          注意,永遠(yuǎn)不要添加unload事件偵聽器!請改用pagehide事件。在Firefox中添加unload事件監(jiān)聽器會使你的站點變慢,而代碼在Chrome和Safari中大部分時間都不會運(yùn)行

          僅僅有條件的添加 beforeunload 事件

          beforeunload事件不會使您的頁面不符合Chrome或Safari的bfcache,但在Firefox中不行,因此除非絕對必要,否則請避免使用它。

          但是,與unload事件不同,beforeunload有合法的用法。例如,當(dāng)您要警告用戶他們有未保存的更改時,如果他們離開頁面,他們將丟失。在這種情況下,建議僅在用戶有未保存的更改時添加beforeunload偵聽器,然后在保存未保存的更改后立即將其刪除。

          下面的寫法是?的(無條件的監(jiān)聽了beforeunload事件):

          window.addEventListener('beforeunload', (event) => {  if (pageHasUnsavedChanges()) {    event.preventDefault();    return event.returnValue = 'Are you sure you want to exit?';  }});

          下面的寫法是?的:

          function beforeUnloadListener(event) {  event.preventDefault();  return event.returnValue = 'Are you sure you want to exit?';};
          // A function that invokes a callback when the page has unsaved changes.// 當(dāng)頁面內(nèi)容未保存時,才監(jiān)聽beforeunload事件onPageHasUnsavedChanges(() => { window.addEventListener('beforeunload', beforeUnloadListener);});
          // A function that invokes a callback when the page's unsaved changes are resolved.// 當(dāng)頁面內(nèi)容保存完畢時,移除beforeunload事件onAllChangesSaved(() => { window.removeEventListener('beforeunload', beforeUnloadListener);});

          避免window.opener的references

          在一些瀏覽器中(包括chrome,從86版本起),如果使用 window.open或者 target=_blank 打開一個新的頁面,但是沒有寫明:rel="noopener",那么,新打開的頁面中,會包含一個對原有頁面的引用。

          除了存在安全風(fēng)險外,保留了對其他頁面引用的頁面不能安全地放入bfcache,因為這可能會破壞任何試圖訪問它的頁面。

          譯者注:安全問題可以參考本公眾號的這篇文章?:

          使用標(biāo)簽時,你可能會忽略的一個安全問題

          因此,最好使用 rel="noopener" 來保證打開的頁面無法引用之前的頁面。如果你的站點需要打開一個新頁面并通過window.postMessage()或直接引用之前的window對象來控制之前的窗口,則新打開的頁面和之前的頁面都不符合bfcache的條件。

          總是在用戶導(dǎo)航離開之前關(guān)閉打開的連接

          如上文所述,當(dāng)一個頁面被放入bfcache時,所有預(yù)定的JavaScript任務(wù)都將暫停,然后在頁面從緩存中取出時繼續(xù)執(zhí)行。

          如果這些計劃的JavaScript任務(wù)只訪問dom api或其他與當(dāng)前頁面隔離的api,那么用戶看不到頁面時,暫停這些任務(wù)不會導(dǎo)致任何問題。

          但是,如果這些任務(wù)涉及到和其他頁面有關(guān)的api(例如:IndexedDB、Web Locks、WebSockets等),則可能會出現(xiàn)問題,因為暫停這些任務(wù)可能會阻止其他選項卡中的代碼運(yùn)行。因此,在以下情況下,大多數(shù)瀏覽器不會嘗試將頁面放入bfcache:

          ?頁面有未完成的indexdb事物?頁面有進(jìn)行中的fetch和ajax請求?頁面具有打開的WebSocket或WebRTC的連接

          如果您的頁面正在使用這些api中的任何一個,那么最好在pagehide或freeze事件期間始終關(guān)閉連接并移除或斷開觀察者。這將允許瀏覽器安全地緩存頁面,而不會影響其他打開的選項卡。

          然后,如果頁面從bfcache恢復(fù),則可以重新打開或重新連接到這些api(在pageshow或resume事件中)。

          測試以確保頁面可緩存

          雖然無法確定頁面在卸載時是否已放入緩存,但可以斷言:后退或前進(jìn)導(dǎo)航確實從緩存中還原了頁面。

          目前,在Chrome中,一個頁面可以在bfcache中停留3分鐘,這應(yīng)該足夠運(yùn)行一個測試(使用puppeter或WebDriver等工具),以確保pageshow事件的persistent屬性在離開頁面并單擊back按鈕后為true。

          請注意,雖然在正常情況下,頁面應(yīng)該在緩存中保留足夠長的時間來運(yùn)行測試,但可以隨時以靜默方式將其逐出(例如,如果系統(tǒng)處于內(nèi)存壓力下)。失敗的測試并不一定意味著頁面不可緩存,因此您需要相應(yīng)地配置測試或構(gòu)建失敗標(biāo)準(zhǔn)。

          退出bfcache的方法

          如果不希望頁面存儲在bfcache中,可以通過將頂級頁面響應(yīng)中的Cache-Control頭設(shè)置為no-store來確保該頁面不會被緩存:

          Cache-Control: no-store

          所有其他緩存指令(包括子frame中的 no-cache 甚至 no-store)不會影響頁面bfcache的資格。

          雖然這個方法是有效的,并且可以跨瀏覽器工作,但是它還有其他緩存和性能影響。為了解決這個問題,有人提議添加一個更明確的退出機(jī)制,包括在需要時清除bfcache的機(jī)制(例如,當(dāng)用戶從共享設(shè)備上的網(wǎng)站注銷時)。

          在Chrome中,可以通過設(shè)置#back-forward-cache?標(biāo)志位來實現(xiàn),或者一個企業(yè)級的證書。

          bfcache如何影響分析和性能度量

          如果你用分析工具跟蹤你的網(wǎng)站訪問量,你可能會注意到報告的頁面瀏覽量減少了,因為Chrome繼續(xù)為更多用戶啟用bfcache。

          事實上,您可能已經(jīng)低估了其他實現(xiàn)bfcache的瀏覽器的頁面瀏覽量,因為大多數(shù)流行的分析庫都不會將bfcache恢復(fù)作為新的頁面視圖進(jìn)行跟蹤。

          如果不希望由于啟用Chrome的bfcache而導(dǎo)致pv數(shù)下降,可以通過監(jiān)聽pageshow事件并檢查persistent屬性,將bfcache還原報告為pv。

          例如:

          // Send a pageview when the page is first loaded.gtag('event', 'page_view')
          window.addEventListener('pageshow', function(event) { if (event.persisted === true) { // Send another pageview if the page is restored from bfcache. gtag('event', 'page_view') }});

          性能度量

          bfcache還可能對字段中收集的性能指標(biāo)產(chǎn)生負(fù)面影響,特別是度量頁面加載時間的指標(biāo)。

          由于bfcache導(dǎo)航還原現(xiàn)有頁面而不是啟動新頁面加載,因此啟用bfcache時收集的頁面加載總數(shù)將減少。但是,關(guān)鍵的是,由bfcache恢復(fù)替換的頁面加載可能是數(shù)據(jù)集中最快的頁面加載。這是因為根據(jù)定義,后退和前進(jìn)導(dǎo)航是重復(fù)訪問,并且重復(fù)頁面加載通常比首次訪問的頁面加載快(由于前面提到的HTTP緩存)。

          結(jié)果是數(shù)據(jù)集中的快速頁面加載更少,這可能會使分布更慢,盡管用戶體驗到的性能可能已經(jīng)提高!

          有幾種方法可以解決這個問題。一種方法是用各自的導(dǎo)航類型注釋所有頁面加載度量:導(dǎo)航、重新加載、后退向前或預(yù)加載。這將允許您繼續(xù)監(jiān)視這些導(dǎo)航類型中的性能,即使總體分布為負(fù)。對于非以用戶為中心的頁面加載指標(biāo),如第一個字節(jié)的時間(TTFB),建議使用這種方法。

          對web核心指標(biāo)的影響

          web核心指標(biāo)衡量用戶在不同維度(加載速度、交互性、視覺穩(wěn)定性)對網(wǎng)頁的體驗,由于用戶體驗到bfcache恢復(fù)比傳統(tǒng)頁面加載更快的導(dǎo)航,因此核心Web Vitals指標(biāo)反映這一點很重要。畢竟,用戶并不關(guān)心bfcache是否被啟用,他們只關(guān)心導(dǎo)航是否快速!

          Chrome用戶體驗報告(Chrome User Experience Report)等工具將很快更新,以將bfcache恢復(fù)視為數(shù)據(jù)集中單獨的頁面訪問。

          雖然在bfcache恢復(fù)后還沒有專門的web性能api來度量這些指標(biāo),但是可以使用現(xiàn)有的web api來近似計算它們的值。

          ?對于最大內(nèi)容繪制(LCP),可以使用pageshow事件的時間戳和下一個繪制幀的時間戳之間的增量(因為幀中的所有元素都將同時繪制)。請注意,對于bfcache還原,LCP和FCP(首幀內(nèi)容繪制)是相同的。?對于First Input Delay(FID),可以在pageshow事件中重新添加事件監(jiān)聽器(與FID polyfill使用的監(jiān)聽器相同),并將FID報告為bfcache還原后第一個輸入的延遲。?對于累積布局移位(CLS),您可以繼續(xù)使用現(xiàn)有的Performance Observer;你只需將當(dāng)前的CLS值重置為0。

          最后



          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          3. 關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。


          點個在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 88
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲第一色网 | 美女黄网站| 免费麻豆国产一区二区三区四区 | 目逼色在线视频 | 韩国毛片一区二区三区 |