前言
瀏覽器緩存對開發者來說一直都是一個有愛又恨的存在,一方面幫助開發者提升用戶體驗,另一方面有時會抽風,讀取緩存展示錯誤的內容,因此,希望對瀏覽器緩存做一個總結,避免開發時因為緩存機制而過多耗費時間。接下來,就進入瀏覽器緩存的世界
什么是緩存
緩存是指一個資源存在于服務器和客戶端之間的副本,緩存會根據請求保存輸出內容的副本,當下一個請求進來的時候,如果是相同的URL,緩存會根據緩存機制決定是直接使用副本響應還是向源服務器重新請求,當你訪問一個網站時,打開調試面板,你會發現幾乎靜態資源請求都是從緩存中讀取出來的,如圖:

為什么要緩存
不用緩存可以嗎?當然可以,至于后果是什么?試了就知道。用了緩存之后,會有什么好處:
- 減少網絡帶寬消耗。無論對于網站運營方還是用戶,帶寬都是和錢掛鉤的,當使用緩存時,產生的網絡流量是極小的,對于兩邊都可以降低開銷
- 降低服務器壓力。當使用緩存時,可以有效減少用戶對源服務器的請求,從而降低服務器壓力
- 加快網頁打開速度,提升用戶體驗。請求緩存比請求源服務器所話費的時間要短的多,因此內容可以更快的觸達用戶,以提升體驗
瀏覽器緩存
前菜結束上硬菜,本篇主角瀏覽器緩存,瀏覽器緩存是眾多web緩存分類中的一種,主要分為:
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
Memory Cache
Memory Cache指的是內存中的緩存,它有如下幾個特點:
- 響應速度最快,是瀏覽器請求時最先去嘗試命中的緩存
- 生命周期短,一旦進程被關閉就會被清空
- 內存有限,資源存放位置隨機
- 不關心資源的HTTP緩存頭的Cache-Control值,在同一個進程會被重用
來看一個例子:


第一張圖是第一次打開網頁時截取的,圈圈標記的圖片時存放在Disk Cache里面的,第二張圖時網頁刷新時截取的,圈圈標記的圖片是存放在Memory Cache中的,同樣的圖片兩次為什么是從不同的緩存中讀取的呢?因為Memory Cache進程關閉時就會清空,但是第二次刷新的時候,第一次瀏覽器解析圖片文件進入Memory Cache,第二次刷新時由于Memory Cache是瀏覽器請求時最先去嘗試命中的緩存,因此會直接去從Memory Cache中取Service Worker Cache
Service Worker是一種獨立于主線程之外的JAVAScript線程,不會對當前程序的執行線程造成堵塞,通過Service Worker我們可以自由控制緩存哪些文件、如何匹配緩存、如何讀取緩存,通過Service Worker實現的緩存稱為Service Worker Cache,如果你對Service Worker有更深入了解的興趣,可以去看看我之前的一篇博客:傳送門
HTTP Cache
HTTP Cache是我們日常開發中接觸最多也是最為熟悉的緩存,HTTP Cache通常可以分為強緩存和協商緩存,緩存策略都是通過HTTP Header來實現的,強緩存的優先級高于協商緩存,在命中強緩存失敗的情況下才會去命中協商緩存
強緩存
強緩存主要通過設置HTTP Header Expires和Cache-Control兩個字段控制,當請求發出時。瀏覽器會根據上一次請求時記錄的Expires和Cache-Control來判斷是否命中強緩存,若命中則直接從強緩存中取資源,不會再向服務發送請求,當命中強緩存時,HTTP狀態碼返回200,且Size顯示from disk cache,如圖所示:

Expires
Expires緩存過期時間,值為一個時間戳。如圖:

當我們兩次向同一個服務器請求資源時,第二次請求時,瀏覽器會先對比Expires時間戳和本地時間,如果本地時間小于Expires時間戳就會去緩存中取資源,但是這樣存在一個問題,Expires依賴客戶端時間,如果客戶端時間和服務端時間不一致時,就會產生問題,Expires是HTTP1.0標準下的字段,考慮到這個問題,因此在HTTP1.1標準下新增了Cache-Control字段Cache-Control
Cache-Control字段是Expires字段的完全替代方案,它做Expires字段能做的所有事,還有Expires字段不能做的事情,當前還在用Expires字段的目的只是向下兼容,Cache-Control字段包含多個指令,這里介紹幾個最常用的:
- max-age:max-age指令控制資源的有效期,值為時間長度,如圖:

- 當客戶端發送的請求中包含max-age指令時,瀏覽器會向服務器確認緩存的有效性,如果判定緩存資源的緩存時間數值比指定的時間數值小,那么客戶端就接收緩存資源,當指定max-age值為0時,通常會向服務器發送請求
- 當服務器返回的響應中包含max-age指令時,表示這段時間內,響應由緩存控制,瀏覽器不會再向服務器確認資源的有效性,而是直接返回緩存
- s-maxage:s-maxage指令的功能和max-age指令的相同,不同點是s-maxage指令只適用于供多位用戶使用的公共緩存服務器(代理服務器),s-maxage的優先級高于maxage,當s-maxage未過期時,會向公共緩存服務器請求緩存
- public指令與private指令:當使用public指令時,則表示此緩存是公有緩存,可以被其他用戶使用,當使用private指令時,表示該緩存時私有緩存,只有在特定用戶請求時才會返回緩存
- no-cache:防止從緩存中返回過期資源,當客戶端請求中包含no-cache指令,表示客戶端將不會接收緩存過的響應,緩存服務器必須把客戶端請求轉發給源服務器,當服務器返回的響應中包含no-cache指令,緩存服務器不能對資源進行緩存,存儲在本地緩存區中緩存在與源服務器進行新鮮度再驗證之前,緩存不能將其提供給客戶端使用
- no-store:不使用任何緩存,直接向源服務器請求下載內容
協商緩存
協商緩存就是強制緩存失效后,瀏覽器瀏覽器攜帶緩存標識向服務發起請求,由服務器更具緩存標識決定是否返回緩存,主要有兩種情況:
- 如果服務端提示資源未改動,資源會被重定向到瀏覽器緩存,這種情況下對應的網絡狀態碼為304,如圖:
- 協商緩存失敗,資源更新了,重新返回請求結果,這種情況下對應的網絡狀態碼為200
接下來介紹和協商緩存相關的頭部字段:
Last-Modified/If-Modified-since
Last-Modified指明資源最終修改的時間,值為一個時間戳,如圖:

當緩存要對已緩存的文檔進行再驗證時,請求頭中就會包含一個If-Modified-since首部,其攜帶有此資源最后修改的時間戳,如圖:

如果在此期間內容被修改,最后的修改日期就會有所不同,源服務器就會返回新的內容重新響應,否則就會返回304,重定向到瀏覽器緩存。
- 使用Last-Modified存在兩個弊端:我們打開了文件,但是并沒有修改文件內容,服務器還是會認為我們修改了這個文件,Last-Modified會被更新,下次請求時會重新響應
- If-Modified-since只能感應以秒為最小單位的時間差,當改動文件速度過快,小于1s時,無法感知文件變化,導致應該重新請求時,拉到緩存資源
由于這些缺陷HTTP1.1出現了Etag/If-None-Match
Etag/If-None-Match
ETag能告知客戶端實體標識,它是一種可將資源以字符串形式做唯一標識的方式。服務器會為每份資源分配對應的ETag值,當資源更新ETag值也會更新,如圖:

當我們下次請求時,請求頭里會帶上一個If-None-Match字符串提供服務端對比,如圖:

如果服務器上的標簽已經發生了變化,服務器會在一個200響應中返回新的內容以及新的ETag,否則返回304重定向到緩存HTTP緩存決策

此圖源自google,清楚展示了HTTP Cache決策的過程,對上面介紹的緩存過程做了一個完美的總結Push Cache
Push Cache指HTTP2在server push階段存在的緩存,是HTTP2 session的一部分,不是一個持久化的緩存,當session結束時,緩存也會隨之結束,不同的頁面只要共享了同一個HTTP2連接,那么它們就可以共享同一個Push Cache,如果你對Push Cache還有更多的興趣,這里提供三篇文章供你閱讀:
- HTTP/2 push is tougher than I thought
- A Tale of Four Caches
- HTTP2 Server Push 的研究
總結
此篇文章記錄總結了瀏覽器緩存相關的一些知識點,是個人最近對緩存知識的一個總結,希望對大家也能有所幫助。
如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊