1. 程式人生 > 實用技巧 >瀏覽器快取機制

瀏覽器快取機制

瀏覽器快取機制

快取可以說是效能優化中簡單高效的一種優化方式了,它可以顯著減少網路傳輸所帶來的損耗。

對於一個數據請求來說,可以分為發起網路請求、後端處理、瀏覽器響應三個步驟。瀏覽器快取可以幫助我們在第一和第三步驟中優化效能。比如說直接使用快取而不發起請求,或者發起了請求但後端儲存的資料和前端一致,那麼就沒有必要再將資料回傳回來,這樣就減少了響應資料。

接下來的內容中我們將通過以下幾個部分來探討瀏覽器快取機制:

  • 快取位置
  • 快取策略
  • 實際場景應用快取策略

快取位置

從快取位置上來說分為四種,並且各自有優先順序,當依次查詢快取且都沒有命中的時候,才會去請求網路

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache
  5. 網路請求

Service Worker

在上一章節中我們已經介紹了 Service Worker 的內容,這裡就不演示相關的程式碼了。

Service Worker 的快取與瀏覽器其他內建的快取機制不同,它可以讓我們自由控制快取哪些檔案、如何匹配快取、如何讀取快取,並且快取是持續性的。

當 Service Worker 沒有命中快取的時候,我們需要去呼叫fetch函式獲取資料。也就是說,如果我們沒有在 Service Worker 命中快取的話,會根據快取查詢優先順序去查詢資料。但是不管我們是從 Memory Cache 中還是從網路請求中獲取的資料,瀏覽器都會顯示我們是從 Service Worker 中獲取的內容。

Memory Cache

Memory Cache 也就是記憶體中的快取,讀取記憶體中的資料肯定比磁碟快。但是記憶體快取雖然讀取高效,可是快取持續性很短,會隨著程序的釋放而釋放。一旦我們關閉 Tab 頁面,記憶體中的快取也就被釋放了。

當我們訪問過頁面以後,再次重新整理頁面,可以發現很多資料都來自於記憶體快取

那麼既然記憶體快取這麼高效,我們是不是能讓資料都存放在記憶體中呢?

先說結論,這是不可能的。首先計算機中的記憶體一定比硬碟容量小得多,作業系統需要精打細算記憶體的使用,所以能讓我們使用的記憶體必然不多。記憶體中其實可以儲存大部分的檔案,比如說 JSS、HTML、CSS、圖片等等。但是瀏覽器會把哪些檔案丟進記憶體這個過程就很玄學了,我查閱了很多資料都沒有一個定論。

當然,我通過一些實踐和猜測也得出了一些結論:

  • 對於大檔案來說,大概率是不儲存在記憶體中的,反之優先
  • 當前系統記憶體使用率高的話,檔案優先儲存進硬碟

Disk Cache

Disk Cache 也就是儲存在硬碟中的快取,讀取速度慢點,但是什麼都能儲存到磁碟中,比之 Memory Cache勝在容量和儲存時效性上。

在所有瀏覽器快取中,Disk Cache 覆蓋面基本是最大的。它會根據 HTTP Herder 中的欄位判斷哪些資源需要快取,哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求。並且即使在跨站點的情況下,相同地址的資源一旦被硬碟快取下來,就不會再次去請求資料。

Push Cache

Push Cache 是 HTTP/2 中的內容,當以上三種快取都沒有命中時,它才會被使用。並且快取時間也很短暫,只在會話(Session)中存在,一旦會話結束就被釋放。

Push Cache 在國內能夠查到的資料很少,也是因為 HTTP/2 在國內不夠普及,但是 HTTP/2 將會是日後的一個趨勢。這裡推薦閱讀HTTP/2 push is tougher than I thought這篇文章,但是內容是英文的,我翻譯一下文章中的幾個結論,有能力的同學還是推薦自己閱讀

  • 所有的資源都能被推送,但是 Edge 和 Safari 瀏覽器相容性不怎麼好
  • 可以推送no-cacheno-store的資源
  • 一旦連線被關閉,Push Cache 就被釋放
  • 多個頁面可以使用相同的 HTTP/2 連線,也就是說能使用同樣的快取
  • Push Cache 中的快取只能被使用一次
  • 瀏覽器可以拒絕接受已經存在的資源推送
  • 你可以給其他域名推送資源

網路請求

如果所有快取都沒有命中的話,那麼只能發起請求來獲取資源了。

那麼為了效能上的考慮,大部分的介面都應該選擇好快取策略,接下來我們就來學習快取策略這部分的內容。

快取策略

通常瀏覽器快取策略分為兩種:強快取和協商快取,並且快取策略都是通過設定 HTTP Header 來實現的。

強快取

強快取可以通過設定兩種 HTTP Header 實現:ExpiresCache-Control。強快取表示在快取期間不需要請求,state code為 200。

Expires

Expires: Wed, 22 Oct 2018 08:41:00 GMT

Expires是 HTTP/1 的產物,表示資源會在Wed, 22 Oct 2018 08:41:00 GMT後過期,需要再次請求。並且Expires受限於本地時間,如果修改了本地時間,可能會造成快取失效。

Cache-control

Cache-control: max-age=30

Cache-Control出現於 HTTP/1.1,優先順序高於Expires。該屬性值表示資源會在 30 秒後過期,需要再次請求。

Cache-Control可以在請求頭或者響應頭中設定,並且可以組合使用多種指令

從圖中我們可以看到,我們可以將多個指令配合起來一起使用,達到多個目的。比如說我們希望資源能被快取下來,並且是客戶端和代理伺服器都能快取,還能設定快取失效時間等等。

接下來我們就來學習一些常見指令的作用

協商快取

如果快取過期了,就需要發起請求驗證資源是否有更新。協商快取可以通過設定兩種 HTTP Header 實現:Last-ModifiedETag

當瀏覽器發起請求驗證資源時,如果資源沒有做改變,那麼服務端就會返回 304 狀態碼,並且更新瀏覽器快取有效期。

Last-Modified 和 If-Modified-Since

Last-Modified表示本地檔案最後修改日期,If-Modified-Since會將Last-Modified的值傳送給伺服器,詢問伺服器在該日期後資源是否有更新,有更新的話就會將新的資源傳送回來,否則返回 304 狀態碼。

但是Last-Modified存在一些弊端:

  • 如果本地開啟快取檔案,即使沒有對檔案進行修改,但還是會造成Last-Modified被修改,服務端不能命中快取導致傳送相同的資源
  • 因為Last-Modified只能以秒計時,如果在不可感知的時間內修改完成檔案,那麼服務端會認為資源還是命中了,不會返回正確的資源

因為以上這些弊端,所以在 HTTP / 1.1 出現了ETag

ETag 和 If-None-Match

ETag類似於檔案指紋,If-None-Match會將當前ETag傳送給伺服器,詢問該資源ETag是否變動,有變動的話就將新的資源傳送回來。並且ETag優先順序比Last-Modified高。

以上就是快取策略的所有內容了,看到這裡,不知道你是否存在這樣一個疑問。如果什麼快取策略都沒設定,那麼瀏覽器會怎麼處理?

對於這種情況,瀏覽器會採用一個啟發式的演算法,通常會取響應頭中的Date減去Last-Modified值的 10% 作為快取時間。

實際場景應用快取策略

單純瞭解理論而不付諸於實踐是沒有意義的,接下來我們來通過幾個場景學習下如何使用這些理論。

頻繁變動的資源

對於頻繁變動的資源,首先需要使用Cache-Control: no-cache使瀏覽器每次都請求伺服器,然後配合ETag或者Last-Modified來驗證資源是否有效。這樣的做法雖然不能節省請求數量,但是能顯著減少響應資料大小。

程式碼檔案

這裡特指除了 HTML 外的程式碼檔案,因為 HTML 檔案一般不快取或者快取時間很短。

一般來說,現在都會使用工具來打包程式碼,那麼我們就可以對檔名進行雜湊處理,只有當代碼修改後才會生成新的檔名。基於此,我們就可以給程式碼檔案設定快取有效期一年Cache-Control: max-age=31536000,這樣只有當 HTML 檔案中引入的檔名發生了改變才會去下載最新的程式碼檔案,否則就一直使用快取。