對前端緩存的研究--------引用

       你們都知道緩存的英文叫作 cache。但我發現一個有趣的現象:這個單詞在不一樣人的口中有不一樣的讀音。爲了全面瞭解緩存,咱們得先從讀音開始,這樣纔可以在和其餘同事(例如 PM)交(zhuāng)流(bī)時體現本身的修(bī)養(gé)css

       基本的網絡請求就是三個步驟:請求,處理,響應。html

        後端緩存主要集中於「處理」步驟,經過保留數據庫鏈接,存儲處理結果等方式縮短處理時間,儘快進入「響應」步驟。前端

        而前端緩存則能夠在剩下的兩步:「請求」和「響應」中進行。在「請求」步驟中,瀏覽器也能夠經過存儲結果的方式直接使用資源,直接省去了發送請求;而「響應」步驟須要瀏覽器和服務器共同配合,經過減小響應內容來縮短傳輸時間。jquery

       按緩存位置分類算法

        緩存的文章會直接從 HTTP 協議頭中的緩存字段開始,例如 Cache-Control, ETag, max-age 等。但偶爾也會聽到別人討論 memory cache, disk cache 等。數據庫

        實際上,HTTP 協議頭的那些字段,都屬於 disk cache 的範疇,是幾個緩存位置的其中之一。所以本着從全局到局部的原則,咱們應當先從緩存位置開始討論。等講到 disk cache 時,纔會詳細講述這些協議頭的字段及其做用。後端

        Network -> Size 一列看到一個請求最終的處理方式:若是是大小 (多少 K, 多少 M 等) 就表示是網絡請求,不然會列出 from memory cache, from disk cache 和 from ServiceWorker。瀏覽器

        

  • Service Worker緩存

  • Memory Cache服務器

  • Disk Cache

  • 網絡請求

memory cache

      memory cache 是內存中的緩存,(與之相對 disk cache 就是硬盤上的緩存)。按照操做系統的常理:先讀內存,再讀硬盤。disk cache 將在後面介紹 (由於它的優先級更低一些),這裏先討論 memory cache。

幾乎全部的網絡請求資源都會被瀏覽器自動加入到 memory cache 中。可是也正由於數量很大可是瀏覽器佔用的內存不能無限擴大這樣兩個因素,memory cache 註定只能是個「短時間存儲」。常規狀況下,瀏覽器的 TAB 關閉後該次瀏覽的 memory cache 便告失效 (爲了給其餘 TAB 騰出位置)。而若是極端狀況下 (例如一個頁面的緩存就佔用了超級多的內存),那可能在 TAB 沒關閉以前,排在前面的緩存就已經失效了。

剛纔提過,幾乎全部的請求資源 都能進入 memory cache,這裏細分一下主要有兩塊:

preloader。若是你對這個機制不太瞭解,這裏作一個簡單的介紹,詳情能夠參閱這篇文章。

熟悉瀏覽器處理流程的同窗們應該瞭解,在瀏覽器打開網頁的過程當中,會先請求 HTML 而後解析。以後若是瀏覽器發現了 js, css 等須要解析和執行的資源時,它會使用 CPU 資源對它們進行解析和執行。在古老的年代(大約 2007 年之前),「請求 js/css - 解析執行 - 請求下一個 js/css - 解析執行下一個 js/css」 這樣的「串行」操做模式在每次打開頁面以前進行着。很明顯在解析執行的時候,網絡請求是空閒的,這就有了發揮的空間:咱們能不能一邊解析執行 js/css,一邊去請求下一個(或下一批)資源呢?

這就是 preloader 要作的事情。不過 preloader 沒有一個官方標準,因此每一個瀏覽器的處理都略有區別。例若有些瀏覽器還會下載 css 中的 @import 內容或者 <video> 的 poster等。

而這些被 preloader 請求夠來的資源就會被放入 memory cache 中,供以後的解析執行操做使用。

preload (雖然看上去和剛纔的 preloader 就差了倆字母)。實際上這個你們應該更加熟悉一些,例如 <link rel="preload">。這些顯式指定的預加載資源,也會被放入 memory cache 中。

memory cache 機制保證了一個頁面中若是有兩個相同的請求 (例如兩個 src 相同的 <img>,兩個 href 相同的 <link>)都實際只會被請求最多一次,避免浪費。

不過在匹配緩存時,除了匹配徹底相同的 URL 以外,還會比對他們的類型,CORS 中的域名規則等。所以一個做爲腳本 (script) 類型被緩存的資源是不能用在圖片 (image) 類型的請求中的,即使他們 src 相等。

在從 memory cache 獲取緩存內容時,瀏覽器會忽視例如 max-age=0, no-cache 等頭部配置。例如頁面上存在幾個相同 src 的圖片,即使它們可能被設置爲不緩存,但依然會從 memory cache 中讀取。這是由於 memory cache 只是短時間使用,大部分狀況生命週期只有一次瀏覽而已。而 max-age=0 在語義上廣泛被解讀爲「不要在下次瀏覽時使用」,因此和 memory cache 並不衝突。

但若是站長是真心不想讓一個資源進入緩存,就連短時間也不行,那就須要使用 no-store。存在這個頭部配置的話,即使是 memory cache 也不會存儲,天然也不會從中讀取了。(後面的第二個示例有關於這點的體現)

       

disk cache

       disk cache 也叫 HTTP cache,顧名思義是存儲在硬盤上的緩存,所以它是持久存儲的,是實際存在於文件系統中的。並且它容許相同的資源在跨會話,甚至跨站點的狀況下使用,例如兩個站點都使用了同一張圖片。

disk cache 會嚴格根據 HTTP 頭信息中的各種字段來斷定哪些資源能夠緩存,哪些資源不能夠緩存;哪些資源是仍然可用的,哪些資源是過期須要從新請求的。當命中緩存以後,瀏覽器會從硬盤中讀取資源,雖然比起從內存中讀取慢了一些,但比起網絡請求仍是快了很多的。絕大部分的緩存都來自 disk cache。

關於 HTTP 的協議頭中的緩存字段,咱們會在稍後進行詳細討論。

凡是持久性存儲都會面臨容量增加的問題,disk cache 也不例外。在瀏覽器自動清理時,會有神祕的算法去把「最老的」或者「最可能過期的」資源刪除,所以是一個一個刪除的。不過每一個瀏覽器識別「最老的」和「最可能過期的」資源的算法不盡相同,可能也是它們差別性的體現。

      

Service Worker

        上述的緩存策略以及緩存/讀取/失效的動做都是由瀏覽器內部判斷 & 進行的,咱們只能設置響應頭的某些字段來告訴瀏覽器,而不能本身操做。舉個生活中去銀行存/取錢的例子來講,你只能告訴銀行職員,我要存/取多少錢,而後把由他們會通過一系列的記錄和手續以後,把錢放到金庫中去,或者從金庫中取出錢來交給你。

但 Service Worker 的出現,給予了咱們另一種更加靈活,更加直接的操做方式。依然以存/取錢爲例,咱們如今能夠繞開銀行職員,本身走到金庫前(固然是有別於上述金庫的一個單獨的小金庫),本身把錢放進去或者取出來。所以咱們能夠選擇放哪些錢(緩存哪些文件),什麼狀況把錢取出來(路由匹配規則),取哪些錢出來(緩存匹配並返回)。固然現實中銀行沒有給咱們開放這樣的服務。

Service Worker 可以操做的緩存是有別於瀏覽器內部的 memory cache 或者 disk cache 的。咱們能夠從 Chrome 的 F12 中,Application -> Cache Storage 找到這個單獨的「小金庫」。除了位置不一樣以外,這個緩存是永久性的,即關閉 TAB 或者瀏覽器,下次打開依然還在(而 memory cache 不是)。有兩種狀況會致使這個緩存中的資源被清除:手動調用 API cache.delete(resource) 或者容量超過限制,被瀏覽器所有清空。

        若是 Service Worker 沒能命中緩存,通常狀況會使用 fetch() 方法繼續獲取資源。這時候,瀏覽器就去 memory cache 或者 disk cache 進行下一次找緩存的工做了。注意:通過 Service Worker 的 fetch() 方法獲取的資源,即使它並無命中 Service Worker 緩存,甚至實際走了網絡請求,也會標註爲 from ServiceWorker。

      

請求網絡

      若是一個請求在上述 3 個位置都沒有找到緩存,那麼瀏覽器會正式發送網絡請求去獲取內容。以後容易想到,爲了提高以後請求的緩存命中率,天然要把這個資源添加到緩存中去。具體來講:

  • 根據 Service Worker 中的 handler 決定是否存入 Cache Storage (額外的緩存位置)。

  • 根據 HTTP 頭部的相關字段(Cache-control, Pragma 等)決定是否存入 disk cache

  • memory cache 保存一份資源 的引用,以備下次使用。

     

    按失效策略分類

       memory cache 是瀏覽器爲了加快讀取緩存速度而進行的自身的優化行爲,不受開發者控制,也不受 HTTP 協議頭的約束,算是一個黑盒。Service Worker 是由開發者編寫的額外的腳本,且緩存位置獨立,出現也較晚,使用還不算太普遍。因此咱們平時最爲熟悉的實際上是 disk cache,也叫 HTTP cache (由於不像 memory cache,它遵照 HTTP 協議頭中的字段)。平時所說的強制緩存,對比緩存,以及 Cache-Control 等,也都歸於此類。

      

強制緩存 (也叫強緩存)

強制緩存的含義是,當客戶端請求後,會先訪問緩存數據庫看緩存是否存在。若是存在則直接返回;不存在則請求真的服務器,響應後再寫入緩存數據庫。

強制緩存直接減小請求數,是提高最大的緩存策略。 它的優化覆蓋了文章開頭提到過的請求數據的所有三個步驟。若是考慮使用緩存來優化網頁性能的話,強制緩存應該是首先被考慮的。

   Expires

   

這是 HTTP 1.0 的字段,表示緩存到期時間,是一個絕對的時間 (當前時間+緩存時間),如
能夠形成強制緩存的字段是 Cache-control 和 Expires。

Expires: Thu, 10 Nov 2017 08:45:11 GMT

在響應消息頭中,設置這個字段以後,就能夠告訴瀏覽器,在未過時以前不須要再次請求。

可是,這個字段設置時有兩個缺點:

  • 因爲是絕對時間,用戶可能會將客戶端本地的時間進行修改,而致使瀏覽器判斷緩存失效,從新請求該資源。此外,即便不考慮自信修改,時差或者偏差等因素也可能形成客戶端與服務端的時間不一致,導致緩存失效。

  • 寫法太複雜了。表示時間的字符串多個空格,少個字母,都會致使非法屬性從而設置失效。

Cache-control

     

已知Expires的缺點以後,在HTTP/1.1中,增長了一個字段Cache-control,該字段表示資源緩存的最大有效時間,在該時間內,客戶端不須要向服務器發送請求

這二者的區別就是前者是絕對時間,然後者是相對時間。以下:

Cache-control: max-age=2592000

下面列舉一些 Cache-control 字段經常使用的值:(完整的列表能夠查看 MDN)

  • max-age:即最大有效時間,在上面的例子中咱們能夠看到

  • must-revalidate:若是超過了 max-age 的時間,瀏覽器必須向服務器發送請求,驗證資源是否還有效。

  • no-cache:雖然字面意思是「不要緩存」,但實際上仍是要求客戶端緩存內容的,只是是否使用這個內容由後續的對比來決定。

  • no-store: 真正意義上的「不要緩存」。全部內容都不走緩存,包括強制和對比。

  • public:全部的內容均可以被緩存 (包括客戶端和代理服務器, 如 CDN)

  • private:全部的內容只有客戶端才能夠緩存,代理服務器不能緩存。默認值。

這些值能夠混合使用,例如 Cache-control:public, max-age=2592000。在混合使用時,它們的優先級以下圖:

     

 

 

 這裏有一個疑問:max-age=0 和 no-cache 等價嗎?從規範的字面意思來講,max-age 到期是 應該(SHOULD) 從新驗證,而 no-cache 是 必須(MUST) 從新驗證。但實際狀況以瀏覽器實現爲準,大部分狀況他們倆的行爲仍是一致的。(若是是 max-age=0, must-revalidate 就和 no-cache 等價了)

順帶一提,在 HTTP/1.1 以前,若是想使用 no-cache,一般是使用 Pragma 字段,如 Pragma: no-cache(這也是 Pragma 字段惟一的取值)。可是這個字段只是瀏覽器約定俗成的實現,並無確切規範,所以缺少可靠性。它應該只做爲一個兼容字段出現,在當前的網絡環境下其實用處已經很小。

總結一下,自從 HTTP/1.1 開始,Expires 逐漸被 Cache-control 取代。Cache-control 是一個相對時間,即便客戶端時間發生改變,相對時間也不會隨之改變,這樣能夠保持服務器和客戶端的時間一致性。並且 Cache-control 的可配置性比較強大。

Cache-control 的優先級高於 Expires,爲了兼容 HTTP/1.0 和 HTTP/1.1,實際項目中兩個字段咱們都會設置。

   

對比緩存 (也叫協商緩存)

     當強制緩存失效(超過規定時間)時,就須要使用對比緩存,由服務器決定緩存內容是否失效。   

     流程上說,瀏覽器先請求緩存數據庫,返回一個緩存標識。以後瀏覽器拿這個標識和服務器通信。若是緩存未失效,則返回 HTTP 狀態碼 304 表示繼續使用,因而客戶端繼續使用緩存;若是失效,則返回新的數據和緩存規則,瀏覽器響應數據後,再把規則寫入到緩存數據庫。

      對比緩存在請求數上和沒有緩存是一致的,但若是是 304 的話,返回的僅僅是一個狀態碼而已,並無實際的文件內容,所以 在響應體體積上的節省是它的優化點。它的優化覆蓋了文章開頭提到過的請求數據的三個步驟中的最後一個:「響應」。經過減小響應體體積,來縮短網絡傳輸時間。因此和強制緩存相比提高幅度較小,但總比沒有緩存好。

     

對比緩存是能夠和強制緩存一塊兒使用的,做爲在強制緩存失效後的一種後備方案。實際項目中他們也的確常常一同出現。

對比緩存有 2 組字段(不是兩個):

Last-Modified & If-Modified-Since

服務器經過 Last-Modified 字段告知客戶端,資源最後一次被修改的時間,例如

Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT

瀏覽器將這個值和內容一塊兒記錄在緩存數據庫中。

下一次請求相同資源時時,瀏覽器從本身的緩存中找出「不肯定是否過時的」緩存。所以在請求頭中將上次的 Last-Modified 的值寫入到請求頭的 If-Modified-Since 字段

服務器會將 If-Modified-Since 的值與 Last-Modified 字段進行對比。若是相等,則表示未修改,響應 304;反之,則表示修改了,響應 200 狀態碼,並返回數據。

可是他仍是有必定缺陷的:

若是資源更新的速度是秒如下單位,那麼該緩存是不能被使用的,由於它的時間單位最低是秒。

若是文件是經過服務器動態生成的,那麼該方法的更新時間永遠是生成的時間,儘管文件可能沒有變化,因此起不到緩存的做用。

Etag & If-None-Match

爲了解決上述問題,出現了一組新的字段 Etag 和 If-None-Match

Etag 存儲的是文件的特殊標識(通常都是 hash 生成的),服務器存儲着文件的 Etag 字段。以後的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新時間改變成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 變成了 If-None-Match。服務器一樣進行比較,命中返回 304, 不命中返回新資源和 200。

Etag 的優先級高於 Last-Modified

緩存小結

當瀏覽器要請求資源時

    1. 調用 Service Worker 的 fetch 事件響應

    2. 查看 memory cache

    3. 查看 disk cache。這裏又細分:

      1. 若是有強制緩存且未失效,則使用強制緩存,不請求服務器。這時的狀態碼所有是 200

      2. 若是有強制緩存但已失效,使用對比緩存,比較後肯定 304 仍是 200

 

  1. 發送網絡請求,等待網絡響應

  2. 把響應內容存入 disk cache (若是 HTTP 頭信息配置能夠存的話)

  3. 把響應內容 的引用 存入 memory cache (無視 HTTP 頭信息的配置)

  4. 把響應內容存入 Service Worker 的 Cache Storage (若是 Service Worker 的腳本調用了 cache.put())

一些案例

光看原理難免枯燥。咱們編寫一些簡單的網頁,經過案例來深入理解上面的那些原理。

memory cache & disk cache

咱們寫一個簡單的 index.html,而後引用 3 種資源,分別是 index.js, index.css 和 mashroom.jpg。

咱們給這三種資源都設置上 Cache-control: max-age=86400,表示強制緩存 24 小時。如下截圖所有使用 Chrome 的隱身模式。

   首次請求

  

 

 

 

   

再次請求 (F5)

 

 

 

第二次請求,三個請求都來自 memory cache。由於咱們沒有關閉 TAB,因此瀏覽器把緩存的應用加到了 memory cache。(耗時 0ms,也就是 1ms 之內)

關閉 TAB,打開新 TAB 並再次請求

 

 

 

      由於關閉了 TAB,memory cache 也隨之清空。可是 disk cache 是持久的,因而全部資源來自 disk cache。(大約耗時 3ms,由於文件有點小)

並且對比 2 和 3,很明顯看到 memory cache 仍是比 disk cache 快得多的。

   no-cache & no-store

咱們在 index.html 裏面一些代碼,完成兩個目標:

  • 每種資源都(同步)請求兩次

  • 增長腳本異步請求圖片

<!-- 把3種資源都改爲請求兩次 -->
<link rel="stylesheet" href="/static/index.css">
<link rel="stylesheet" href="/static/index.css">
<script src="/static/index.js"></script>
<script src="/static/index.js"></script>
<img src="/static/mashroom.jpg">
<img src="/static/mashroom.jpg">
<!-- 異步請求圖片 -->
<script>
   setTimeout(function () {
       let img = document.createElement('img')
       img.src = '/static/mashroom.jpg'
       document.body.appendChild(img)
   }, 1000)
</script>

當把服務器響應設置爲 Cache-Control: no-cache 時,咱們發現打開頁面以後,三種資源都只被請求 1 次。

 

 

 

 

這說明兩個問題:

  • 同步請求方面,瀏覽器會自動把當次 HTML 中的資源存入到緩存 (memory cache),這樣碰到相同 src 的圖片就會自動讀取緩存(但不會在 Network 中顯示出來)

  • 異步請求方面,瀏覽器一樣是不發請求而直接讀取緩存返回。但一樣不會在 Network 中顯示。

整體來講,如上面原理所述,no-cache 從語義上表示下次請求不要直接使用緩存而須要比對,並不對本次請求進行限制。所以瀏覽器在處理當前頁面時,能夠放心使用緩存。

當把服務器響應設置爲 Cache-Control: no-store 時,狀況發生了變化,三種資源都被請求了 2 次。而圖片由於還多一次異步請求,總計 3 次。(紅框中的都是那一次異步請求)

  

 

 

 

 

   這一樣說明:

  • 如以前原理所述,雖然 memory cache 是無視 HTTP 頭信息的,可是 no-store 是特別的。在這個設置下,memory cache 也不得不每次都請求資源。

  • 異步請求和同步遵循相同的規則,在 no-store 狀況下,依然是每次都發送請求,不進行任何緩存。

    Service Worker & memory (disk) cache

   咱們嘗試把 Service Worker 也加入進去。咱們編寫一個 serviceWorker.js,並編寫以下內容:(主要是預緩存 3 個資源,並在實際請求時匹配緩存並返回)

// serviceWorker.js
self.addEventListener('install', e => {
 // 當肯定要訪問某些資源時,提早請求並添加到緩存中。
 // 這個模式叫作「預緩存」
 e.waitUntil(
   caches.open('service-worker-test-precache').then(cache => {
     return cache.addAll(['/static/index.js', '/static/index.css', '/static/mashroom.jpg'])
   })
 )
})
self.addEventListener('fetch', e => {
 // 緩存中能找到就返回,找不到就網絡請求,以後再寫入緩存並返回。
 // 這個稱爲 CacheFirst 的緩存策略。
 return e.respondWith(
   caches.open('service-worker-test-precache').then(cache => {
     return cache.match(e.request).then(matchedResponse => {
       return matchedResponse || fetch(e.request).then(fetchedResponse => {
         cache.put(e.request, fetchedResponse.clone())
         return fetchedResponse
       })
     })
   })
 )
})

註冊 SW 的代碼這裏就不贅述了。此外咱們還給服務器設置 Cache-Control: max-age=86400 來開啓 disk cache。咱們的目的是看看二者的優先級。

當咱們首次訪問時,會看到常規請求以外,瀏覽器(確切地說是 Service Worker)額外發出了 3 個請求。這來自預緩存的代碼。

 

 第二次訪問(不管關閉 TAB 從新打開,仍是直接按 F5 刷新)都能看到全部的請求標記爲 from SerciceWorker。

  

 

 from ServiceWorker 只表示請求經過了 Service Worker,至於究竟是命中了緩存,仍是繼續 fetch() 方法光看這一條記錄其實無從知曉。所以咱們還得配合後續的 Network 記錄來看。由於以後沒有額外的請求了,所以斷定是命中了緩存。

 

 從服務器的日誌也能很明顯地看到,3 個資源都沒有被從新請求,即命中了 Service Worker 內部的緩存。

 

若是修改 serviceWorker.js 的 fetch 事件監聽代碼,改成以下:

// 這個也叫作 NetworkOnly 的緩存策略。
self.addEventListener('fetch', e => {
 return e.respondWith(fetch(e.request))
})

能夠發如今後續訪問時的效果和修改前是 徹底一致的。(即 Network 僅有標記爲 from ServiceWorker 的幾個請求,而服務器也不打印 3 個資源的訪問日誌)

很明顯 Service Worker 這層並無去讀取本身的緩存,而是直接使用 fetch() 進行請求。因此此時實際上是 Cache-Control: max-age=86400 的設置起了做用,也就是 memory/disk cache。但具體是 memory 仍是 disk 這個只有瀏覽器本身知道了,由於它並無顯式的告訴咱們。(我的猜想是 memory,由於不論從耗時 0ms 仍是從不關閉 TAB 來看,都更像是 memory cache)

瀏覽器的行爲

所謂瀏覽器的行爲,指的就是用戶在瀏覽器如何操做時,會觸發怎樣的緩存策略。主要有 3 種:

  • 打開網頁,地址欄輸入地址: 查找 disk cache 中是否有匹配。若有則使用;如沒有則發送網絡請求。

  • 普通刷新 (F5):由於 TAB 並無關閉,所以 memory cache 是可用的,會被優先使用(若是匹配的話)。其次纔是 disk cache。

  • 強制刷新 (Ctrl + F5):瀏覽器不使用緩存,所以發送的請求頭部均帶有 Cache-control: no-cache(爲了兼容,還帶了 Pragma: no-cache)。服務器直接返回 200 和最新內容。

緩存的應用模式

瞭解了緩存的原理,咱們可能更加關心如何在實際項目中使用它們,才能更好的讓用戶縮短加載時間,節約流量等。這裏有幾個經常使用的模式,供你們參考

模式 1:不常變化的資源
Cache-Control: max-age=31536000

一般在處理這類資源資源時,給它們的 Cache-Control 配置一個很大的 max-age=31536000 (一年),這樣瀏覽器以後請求相同的 URL 會命中強制緩存。而爲了解決更新的問題,就須要在文件名(或者路徑)中添加 hash, 版本號等動態字符,以後更改動態字符,達到更改引用 URL 的目的,從而讓以前的強制緩存失效 (其實並未當即失效,只是再也不使用了而已)。

在線提供的類庫 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均採用這個模式。若是配置中還增長 public 的話,CDN 也能夠緩存起來,效果拔羣。

這個模式的一個變體是在引用 URL 後面添加參數 (例如 ?v=xxx 或者 ?_=xxx),這樣就沒必要在文件名或者路徑中包含動態參數,知足某些完美主義者的喜愛。在項目每次構建時,更新額外的參數 (例如設置爲構建時的當前時間),則能保證每次構建後總能讓瀏覽器請求最新的內容。

特別注意: 在處理 Service Worker 時,對待 sw-register.js(註冊 Service Worker) 和 serviceWorker.js (Service Worker 自己) 須要格外的謹慎。若是這兩個文件也使用這種模式,你必須多多考慮往後可能的更新及對策。

模式 2:常常變化的資源
Cache-Control: no-cache

這裏的資源不僅僅指靜態資源,也多是網頁資源,例如博客文章。這類資源的特色是:URL 不能變化,但內容能夠(且常常)變化。咱們能夠設置 Cache-Control: no-cache 來迫使瀏覽器每次請求都必須找服務器驗證資源是否有效。

既然提到了驗證,就必須 ETag 或者 Last-Modified 出場。這些字段都會由專門處理靜態資源的經常使用類庫(例如 koa-static)自動添加,無需開發者過多關心。

也正如上文中提到協商緩存那樣,這種模式下,節省的並非請求數,而是請求體的大小。因此它的優化效果不如模式 1 來的顯著。

模式 3:很是危險的模式 1 和 2 的結合 (反例)
Cache-Control: max-age=600, must-revalidate

不知道是否有開發者從模式 1 和 2 得到一些啓發:模式 2 中,設置了 no-cache,至關於 max-age=0, must-revalidate。個人應用時效性沒有那麼強,但又不想作過於長久的強制緩存,我能不能配置例如 max-age=600, must-revalidate 這樣折中的設置呢?

表面上看這很美好:資源能夠緩存 10 分鐘,10 分鐘內讀取緩存,10 分鐘後和服務器進行一次驗證,集兩種模式之大成,但實際線上暗存風險。由於上面提過,瀏覽器的緩存有自動清理機制,開發者並不能控制。

舉個例子:當咱們有 3 種資源: index.html, index.js, index.css。咱們對這 3 者進行上述配置以後,假設在某次訪問時,index.js 已經被緩存清理而不存在,但 index.html, index.css 仍然存在於緩存中。這時候瀏覽器會向服務器請求新的 index.js,而後配上老的 index.html, index.css 展示給用戶。這其中的風險顯而易見:不一樣版本的資源組合在一塊兒,報錯是極有可能的結局。

除了自動清理引起問題,不一樣資源的請求時間不一樣也能致使問題。例如 A 頁面請求的是 A.js 和 all.css,而 B 頁面是 B.js 和 all.css。若是咱們以 A -> B 的順序訪問頁面,勢必致使 all.css 的緩存時間早於 B.js。那麼之後訪問 B 頁面就一樣存在資源版本失配的隱患。

後記

         HTTP 協議中的緩存,Service Worker,以及 Chrome 瀏覽器的一些優化 (Memory Cache)。

相關文章
相關標籤/搜索