緩存一直是前端優化的主戰場,利用好緩存就成功了一半。本篇從HTTP請求和響應的頭域入手,讓你對瀏覽器緩存有個整體的概念。終你會發現強緩存,協商緩存 和 啟發式緩存是如此的簡單。
瀏覽器對于請求資源,擁有一系列成熟的緩存策略。按照發生的時間順序分別為存儲策略、過期策略、協商策略,其中存儲策略在收到響應后應用,過期策略,協商策略在發送請求前應用。流程圖如下所示。
廢話不多說,我們先來看兩張表格。
1.HTTP Header中與緩存有關的Key。
| key | 描述 | 存儲策略 | 過期策略 | 協商策略 |
|---|---|---|---|---|
| Cache-Control | 指定緩存機制,覆蓋其它設置 | ?? | ?? | |
| Pragma | http1.0字段,指定緩存機制 | ?? | ||
| Expires | http1.0字段,指定緩存的過期時間 | ?? | ||
| Last-Modified | 資源后一次的修改時間 | ?? | ||
| ETag | 標識請求資源的字符串 | ?? |
2.緩存協商策略用于重新驗證緩存資源是否有效,有關的Key如下。
| key | 描述 |
|---|---|
| If-Modified-Since | 緩存校驗字段,值為資源后一次的修改時間,即上次收到的Last-Modified值 |
| If-Unmodified-Since | 同上,處理方式與之相反 |
| If-Match | 緩存校驗字段,值為標識請求資源的字符串,即上次收到的ETag值 |
| If-None-Match | 同上,處理方式與之相反 |
下面我們來看下各個頭域(key)的作用。
瀏覽器緩存里,Cache-Control是金字塔頂尖的規則,它藐視一切其他設置,只要其他設置與其抵觸,一律覆蓋之。
不僅如此,它還是一個復合規則,包含多種值,橫跨 存儲策略,過期策略 兩種,同時在請求頭和響應頭都可設置。
語法為: “Cache-Control : cache-directive”。
Cache-directive共有如下12種(其中請求中指令7種,響應中指令9種):
| Cache-directive | 描述 | 存儲策略 | 過期策略 | 請求字段 | 響應字段 |
|---|---|---|---|---|---|
| public | 資源將被客戶端和代理服務器緩存 | ?? | ?? | ||
| private | 資源僅被客戶端緩存,代理服務器不緩存 | ?? | ?? | ||
| no-store | 請求和響應都不緩存 | ?? | ?? | ?? | |
| no-cache |
相當于max-age:0,must-revalidate即資源被緩存,但是緩存立刻過期,同時下次訪問時強制驗證資源有效性
|
?? | ?? | ?? | ?? |
| max-age | 緩存資源,但是在指定時間(單位為秒)后緩存過期 | ?? | ?? | ?? | ?? |
| s-maxage | 同上,依賴public設置,覆蓋max-age,且只在代理服務器上有效。 | ?? | ?? | ?? | |
| max-stale | 指定時間內,即使緩存過時,資源依然有效 | ?? | ?? | ||
| min-fresh | 緩存的資源至少要保持指定時間的新鮮期 | ?? | ?? | ||
| must-revalidation /proxy-revalidation | 如果緩存失效,強制重新向服務器(或代理)發起驗證(因為max-stale等字段可能改變緩存的失效時間) | ?? | ?? | ||
| only-if-cached | 僅僅返回已經緩存的資源,不訪問網絡,若無緩存則返回504 | ?? | |||
| no-transform |
強制要求代理服務器不要對資源進行轉換,禁止代理服務器對Content-Encoding,Content-Range,Content-Type字段的修改(因此代理的gzip壓縮將不被允許)
|
?? | ?? |
假設所請求資源于4月5日緩存,且在4月12日過期。
當max-age 與 max-stale 和 min-fresh 同時使用時,它們的設置相互之間獨立生效,為保守的緩存策略總是有效。這意味著,如果max-age=10 days,max-stale=2 days,min-fresh=3 days,那么:
由于客戶端總是采用保守的緩存策略,因此,4月9日后,對于該資源的請求將重新向服務器發起驗證。
HTTP1.0字段,通常設置為Pragma:no-cache,作用同Cache-Control:no-cache。當一個no-cache請求發送給一個不遵循HTTP/1.1的服務器時,客戶端應該包含pragma指令。為此,勾選?? 上disable cache時,瀏覽器自動帶上了pragma字段。如下:
Expires:Wed, 05 Apr 2017 00:55:35 GMT
即到期時間,以服務器時間為參考系,其優先級比 Cache-Control:max-age 低,兩者同時出現在響應頭時,Expires將被后者覆蓋。如果Expires,Cache-Control: max-age,或 Cache-Control:s-maxage 都沒有在響應頭中出現,并且也沒有其它緩存的設置,那么瀏覽器默認會采用一個啟發式的算法,通常會取響應頭的Date_value - Last-Modified_value值的10%作為緩存時間。
如下資源便采取了啟發式緩存算法。
其緩存時間為 `(Date_value - Last-Modified_value) * 10%,計算如下:
const Date_value = new Date('Thu, 06 Apr 2017 01:30:56 GMT').getTime(); const LastModified_value = new Date('Thu, 01 Dec 2016 06:23:23 GMT').getTime(); const cacheTime = (Date_value - LastModified_value) / 10; const Expires_timestamp = Date_value + cacheTime; const Expires_value = new Date(Expires_timestamp);
console.log('Expires:', Expires_value); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (CST)
可見該資源將于2017年4月18日23點25分41秒過期,嘗試以下兩步進行驗證:
1) 試著把本地時間修改為2017年4月18日23點25分40秒,迅速刷新頁面,發現強緩存依然有效(依舊是200 OK (from disk cache))。
2) 然后又修改本地時間為2017年4月18日23點26分40秒(即往后撥1分鐘),刷新頁面,發現緩存已過期,此時瀏覽器重新向服務器發起了驗證,且命中了304協商緩存,如下所示。
3) 將本地時間恢復正常(即 2017-04-06 09:54:19)。刷新頁面,發現Date依然是4月18日,如下所示。
從?? Provisional headers are shown 和Date字段可以看出來,瀏覽器并未發出請求,緩存依然有效,只不過此時Status Code顯示為200 OK。(甚至我還專門打開了charles,也沒有發現該資源的任何請求,可見這個200 OK多少有些誤導人的意味)
可見,啟發式緩存算法采用的緩存時間可長可短,因此對于常規資源,建議明確設置緩存時間(如指定max-age 或 expires)。
ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"
實體標簽,服務器資源的標識符,瀏覽器可以根據ETag值緩存數據,節省帶寬。如果資源已經改變,etag可以幫助防止同步更新資源的相互覆蓋。ETag 優先級比 Last-Modified 高。
語法: If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …
緩存校驗字段,其值為上次收到的一個或多個etag 值。常用于判斷條件是否滿足,如下兩種場景:
If-Match 可用于阻止錯誤的更新操作,如果不匹配,服務器將返回一個412(Precondition Failed)狀態碼的響應。
語法: If-None-Match: ETag_value 或者 If-None-Match: ETag_value, ETag_value, …
緩存校驗字段,結合ETag字段,常用于判斷緩存資源是否有效,優先級比If-Modified-Since高。
Cache-Control,Content-Location,Date,ETag,Expires,and Vary 中之一的字段。
語法: Last-Modified: 星期,日期 月份 年份 時:分:秒 GMT
Last-Modified: Tue, 04 Apr 2017 10:01:15 GMT
用于標記請求資源的后一次修改時間,格式為GMT(格林尼治標準時間)。如可用 new Date().toGMTString()獲取當前GMT時間。Last-Modified 是 ETag 的fallback機制,優先級比 ETag 低,且只能精確到秒,因此不太適合短時間內頻繁改動的資源。不僅如此,服務器端的靜態資源,通常需要編譯打包,可能出現資源內容沒有改變,而Last-Modified卻改變的情況。
語法同上,如:
If-Modified-Since: Tue, 04 Apr 2017 10:12:27 GMT
緩存校驗字段,其值為上次響應頭的Last-Modified值,若與請求資源當前的Last-Modified值相同,那么將返回304狀態碼的響應,反之,將返回200狀態碼響應。
緩存校驗字段,語法同上。表示資源未修改則正常執行更新,否則返回412(Precondition Failed)狀態碼的響應。常用于如下兩種場景:
一旦資源命中強緩存,瀏覽器便不會向服務器發送請求,而是直接讀取緩存。Chrome下的現象是 200 OK (from disk cache)或者 200 OK (from memory cache)。如下:
對于常規請求,只要存在該資源的緩存,且Cache-Control:max-age 或者expires沒有過期,那么就能命中強緩存。
緩存過期后,繼續請求該資源,對于現代瀏覽器,擁有如下兩種做法:
If-None-Match字段。服務器收到請求后,拿If-None-Match字段的值與資源的ETag值進行比較,若相同,則命中協商緩存,返回304響應。
If-Modified-Since字段。服務器收到請求后,拿If-Modified-Since字段的值與資源的Last-Modified值進行比較,若相同,則命中協商緩存,返回304響應。
以上,ETag優先級比Last-Modified高,同時存在時,前者覆蓋后者。下面通過實例來理解下強緩存和協商緩存。
如下忽略首次訪問,第二次通過 If-Modified-Since 命中了304協商緩存。
協商緩存的響應結果,不僅驗證了資源的有效性,同時還更新了瀏覽器緩存。主要更新內容如下:
Age:0
Cache-Control:max-age=600
Date: Wed, 05 Apr 2017 13:09:36 GMT Expires:Wed, 05 Apr 2017 00:55:35 GMT
Age:0 表示命中了代理服務器的緩存,age值為0表示代理服務器剛剛刷新了一次緩存。
Cache-Control:max-age=600 覆蓋 Expires 字段,表示從Date_value,即 Wed, 05 Apr 2017 13:09:36 GMT 起,10分鐘之后緩存過期。因此10分鐘之內訪問,將會命中強緩存,如下所示:
當然,除了上述與緩存直接相關的字段外,http header中還包括如下間接相關的字段。
出現此字段,表示命中代理服務器的緩存。它指的是代理服務器對于請求資源的已緩存時間,單位為秒。如下:
Age:2383321
Date:Wed, 08 Mar 2017 16:12:42 GMT
以上指的是,代理服務器在2017年3月8日16:12:42時向源服務器發起了對該資源的請求,目前已緩存了該資源2383321秒。
指的是響應生成的時間。請求經過代理服務器時,返回的Date未必是新的,通常這個時候,代理服務器將增加一個Age字段告知該資源已緩存了多久。
對于服務器而言,資源文件可能不止一個版本,比如說壓縮和未壓縮,針對不同的客戶端,通常需要返回不同的資源版本。比如說老式的瀏覽器可能不支持解壓縮,這個時候,就需要返回一個未壓縮的版本; 對于新的瀏覽器,支持壓縮,返回一個壓縮的版本,有利于節省帶寬,提升體驗。那么怎么區分這個版本呢,這個時候就需要Vary了。
服務器通過指定Vary: Accept-Encoding,告知代理服務器,對于這個資源,需要緩存兩個版本: 壓縮和未壓縮。這樣老式瀏覽器和新的瀏覽器,通過代理,就分別拿到了未壓縮和壓縮版本的資源,避免了都拿同一個資源的尷尬。
Vary:Accept-Encoding,User-Agent
如上設置,代理服務器將針對是否壓縮和瀏覽器類型兩個維度去緩存資源。如此一來,同一個url,就能針對PC和Mobile返回不同的緩存內容。
實際上,工作中很多場景都需要避免瀏覽器緩存,除了瀏覽器隱私模式,請求時想要禁用緩存,還可以設置請求頭:Cache-Control: no-cache, no-store, must-revalidate 。
當然,還有一種常用做法: 即給請求的資源增加一個版本號,如下:
<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>
這樣做的好處就是你可以自由控制什么時候加載新的資源。
不僅如此,HTML也可以禁用緩存,即在頁面的\
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>
上述雖能禁用緩存,但只有部分瀏覽器支持,而且由于代理不解析HTML文檔,故代理服務器也不支持這種方式。
實際上,上述緩存有關的規律,并非所有瀏覽器都完全遵循。比如說IE8。
資源緩存是否有效相關。
| 瀏覽器 | 前提 | 操作 | 表現 | 正常表現 |
|---|---|---|---|---|
| IE8 | 資源緩存有效 | 新開一個窗口加載網頁 | 重新發送請求(返回200) | 展示緩存的頁面 |
| IE8 | 資源緩存失效 | 原瀏覽器窗口中單擊 Enter 按鈕 | 展示緩存的頁面 | 重新發送請求(返回200) |
Last-Modified / E-Tag 相關。
| 瀏覽器 | 前提 | 操作 | 表現 | 正常表現 |
|---|---|---|---|---|
| IE8 | 資源內容沒有修改 | 新開一個窗口加載網頁 | 瀏覽器重新發送請求(返回200) | 重新發送請求(返回304) |
| IE8 | 資源內容已修改 | 原瀏覽器窗口中單擊 Enter 按鈕 | 瀏覽器展示緩存的頁面 |
重新發送請求(返回200) |
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。