瀏覽器快取機制
瀏覽器快取機制
快取可以說是效能優化中簡單高效的一種優化方式了,它可以顯著減少網路傳輸所帶來的損耗。
對於一個數據請求來說,可以分為發起網路請求、後端處理、瀏覽器響應三個步驟。瀏覽器快取可以幫助我們在第一和第三步驟中優化效能。比如說直接使用快取而不發起請求,或者發起了請求但後端儲存的資料和前端一致,那麼就沒有必要再將資料回傳回來,這樣就減少了響應資料。
接下來的內容中我們將通過以下幾個部分來探討瀏覽器快取機制:
- 快取位置
- 快取策略
- 實際場景應用快取策略
快取位置
從快取位置上來說分為四種,並且各自有優先順序,當依次查詢快取且都沒有命中的時候,才會去請求網路
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 網路請求
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-cache
和no-store
的資源 - 一旦連線被關閉,Push Cache 就被釋放
- 多個頁面可以使用相同的 HTTP/2 連線,也就是說能使用同樣的快取
- Push Cache 中的快取只能被使用一次
- 瀏覽器可以拒絕接受已經存在的資源推送
- 你可以給其他域名推送資源
網路請求
如果所有快取都沒有命中的話,那麼只能發起請求來獲取資源了。
那麼為了效能上的考慮,大部分的介面都應該選擇好快取策略,接下來我們就來學習快取策略這部分的內容。
快取策略
通常瀏覽器快取策略分為兩種:強快取和協商快取,並且快取策略都是通過設定 HTTP Header 來實現的。
強快取
強快取可以通過設定兩種 HTTP Header 實現:Expires
和Cache-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-Modified
和ETag
。
當瀏覽器發起請求驗證資源時,如果資源沒有做改變,那麼服務端就會返回 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 檔案中引入的檔名發生了改變才會去下載最新的程式碼檔案,否則就一直使用快取。