個人文章彙總在blog
概要
快取是一個非常重要同樣也非常複雜的瀏覽器特性
在這篇文章中,我們將解釋瀏覽器是如何利用快取來使載入頁面更快,哪些因素決定了快取的週期,在必要的時候如何去避開快取。
為什麼快取會如此重要呢?
所有的瀏覽器都會嘗試去快取靜態資源的本地副本,以此去降低頁面的載入時間以及體積最小的網路傳輸
不論服務是在相同的網路環境下還是在世界上其他的網路環境下,從本地快取中取資源一定會比通過網路請求的方式要快
如何讓瀏覽器快取生效呢?
case1:使用者之前沒有訪問過這個站點
瀏覽器針對此站點沒有任何的快取檔案,所以瀏覽器會向站點服務去請求所有所需的資源
下面是一張首次訪問維基百科首頁的資源下載完之後的截圖,底部狀態列顯示出有265kb的資料傳輸到了瀏覽器中
case2:使用者之前訪問過站點
瀏覽器會去請求站點伺服器取到html頁面,然後會檢視是否有對應靜態資源的快取(js, css, images)
當我們重新重新整理維基百科的頁面時,就能看出因為快取的原因與之前的請求狀況不同的地方
此時資料的傳輸量已經降到了928bytes,相當於首頁訪問頁面時的0.3%,Size這一列就顯示出了我們的大多數的資源都來自於快取
Chrome即會在memory cache中去拉取內容,也會在disk cache中去拉取內容,因為在case1和case2中,我們還沒有關閉瀏覽器,所以資料依然會儲存在memory cache中
顯示瀏覽器快取
在chrome中,我們可以在位址列中輸入chrome://cache去檢視快取的內容,對於每一個已經快取的檔案,這裡都將會顯示一個頁面連結,頁面連結的內容包含一個更加詳細的檢視說明。
瀏覽器是如何知道什麼該快取的
瀏覽器先去檢查伺服器所生成的http響應頭資訊,一般用於快取相關的頭資訊有4個:
- ETag
- Cache-Control
- Expires
- Last-Modified
ETag
ETag(Entity Tag)是一個用於快取校驗token的字串,它通常以檔案的雜湊值來表示
伺服器可以新增ETag到它的響應裡,瀏覽器接收到這個響應,那在未來的請求中(在檔案過期之後),瀏覽器就可以根據這個欄位去判斷快取中是否保留著一份過期的副本
如果通過對比hash值是相同的,那麼就說明這個資源沒有變化,伺服器就會返回一個304狀態碼(Not Modified),瀏覽器就知道當前快取的副本是安全的
注意:只有當快取中的檔案過期了,ETag才會被用在請求中
Cache-Control
Cache-Control頭擁有許多指令,利用這些指令我們可以設定快取的行為,過期時間,驗證等,同時這些也可以組合起來一起進行設定。
Cache Behavior(快取行為)
Cache-Control: public
複製程式碼
public意味著資源可以被任意快取(瀏覽器,CDN等)
Cache-Control: private
複製程式碼
private意味著資源只能被瀏覽器快取
Cache-Control: no-store
複製程式碼
no-store意味著讓瀏覽器總是去請求伺服器以獲取資源
Cache-Control: no-cache
複製程式碼
no-cache有一點誤導性,它並不是說“不要快取”
這是在告訴瀏覽器去快取這個檔案,但在和伺服器確認最新版本之前不要去使用它。這個驗證的過程是通過ETag來完成的
這個行為一般會用在html檔案中,因為瀏覽器總會去檢查最新的html檔案標識,所以這一點是講得通的
Expiration
Cache-Control: max-age=60
複製程式碼
這個指令指定了資源應該被快取多少秒,所以max-age=60就意味著資源應該被快取一分鐘,RFC 2616推薦這裡的最大值不要超過一年(max-age=31536000)
Cache-Control: s-max-age=60
複製程式碼
這個指令僅僅是被用在中間快取(比如CDN)
Validation
Cache-Control: must-revalidate
複製程式碼
這個指令告訴快取在使用它之前必須去驗證資源的過期狀態,如果已經過期就不應該被使用
Expires
Expires頭部來源於http1.0版本,但是在當今許多站點中依然保留著
這個頭部欄位指定了一個日期,超過這個日期就代表資源是無效的
Expires: Wed, 25 Jul 2018 21:00:00 GMT
複製程式碼
注意:如果已經指定了Cache-Control中的max-age指令,那瀏覽器就會忽略expires
Last-Modified
Last-Modified頭部也是來自http1.0版本
Last-Modified: Mon, 12 Dec 2016 14:45:00 GMT
複製程式碼
這個欄位包含了資源最後修改的日期和時間
HTML Meta Tag
在HTML5版本之前,在html中使用元標籤(meta tag)制定cache-control是一個有效的方式
<meta http-equiv="Cache-control" content="no-cache">
複製程式碼
但在html5中已經不推薦這麼做了,為什麼?因為只有瀏覽器可以識別這個標籤,而中間快取(CDN)是識別不了的。
所以最好是都通過http頭的方式去傳送快取指令
HTTP Response
讓我們來看一個簡單的http響應
Accept-Ranges: bytes
Cache-Control: max-age=3600
Connection: Keep-Alive
Content-Length: 4361
Content-Type: image/png
Date: Tue, 25 Jul 2017 17:26:16 GMT
ETag: "1109-554221c5c8540"
Expires: Tue, 25 Jul 2017 18:26:16 GMT
Keep-Alive: timeout=5, max=93
Last-Modified: Wed, 12 Jul 2017 17:26:05 GMT
Server: Apache
複製程式碼
- 第2行告訴我們max-age為1個小時
- 第5行告訴我們這是一個png圖片資源
- 第7行告訴我們ETag將在1個小時之後去驗證資源是否改變過
- 第8行,Expires頭會被忽略,因為已經設定了Cache-Control: max-age=3600
- 第10行,Last-Modified頭展示了圖片資源的最後修改時間
快取的誤區
所以我們已經意識到快取是個好東西,我們應該積極利用它
但是我們希望的是,不需要讓使用者每次都去重新整理(Ctrl + F5)或者清空快取才能看到我們頁面的最新內容
這類快取問題經常困擾者開發者以及使用者,使用者可能會看到一個錯亂的頁面或者是一個行為怪異的按鈕,因為他們當前使用的已經是過期的靜態資原始檔
過期的檔案(Stale Files)
下面這張截圖描述了一個銀行網站的使用者與Chase Support之間的交流,可以看到是有關登陸表單出了問題
該使用者很有可能還在使用老的js檔案,這就是導致點選登陸按鈕是表單重置操作而不是表單提交操作
讓我們來探索過期檔案影響我們的另外一種情況。
假設我們在一個叫做app.min.js的檔案中修復了一個bug,並且把它推送到了生產環境
在HTML中,該指令碼是這個樣子
<script src="assets/js/app.min.js">
複製程式碼
我們的web伺服器給這個js檔案設定了max-age為1周(604800秒)的過期時間
Cache-Control: private, max-age=604800
複製程式碼
在更新完該js檔案之後,一些使用者仍在反饋說那個bug依然存在。 那這裡到底發生了什麼呢?
- Bob兩個星期以前訪問了這個網站並且快取一個有問題的app.min.js檔案的副本,由於他的副本已經過期,所以瀏覽器回去請求伺服器,去拿最新的已經修復了bug的版本檔案。
- Mary在兩天之前訪問了該網站,同樣快取了一個有問題的app.min.js檔案的副本,她的副本由於還沒過期,所以瀏覽器還是會去使用快取中的那個副本
在下一個章節中,我們將會利用一個叫做"cache busting"的技術來解決這些問題和情況
Cache Busting
Cache busting會使一個資原始檔失效,強制瀏覽器去伺服器端重新獲取資料。
我們可以通過改變檔名來命令瀏覽器去避開快取,對於瀏覽器來說,這是一份完全新的資源,所以瀏覽器會去服務端請求最新的資料
Cache busting同樣允許我們設定一個比較長的max-age值針對頻繁改動的資源。Google推薦max-age被設為1年(source)
版本號
我們可以給檔名新增一個版本號
assets/js/app-v2.min.js
複製程式碼
指紋(finerprinting)
我們可以基於檔案內容新增一個指紋值
assets/js/app-d41d8cd98f00b204e9800998ecf8427e.min.js
複製程式碼
拼接查詢引數
我們可以在檔名的末尾拼接一個查詢引數
assets/js/app.min.js?version=2
複製程式碼
拼接查詢引數的方式在和代理伺服器互動時有明顯的錯誤(known issues),所以這種方式一般不推薦使用
最佳實踐
推薦
- 對於靜態資源來說,使用Cache-Control和ETag頭部來控制快取的行為
- 設定一個比較長的max-age值,以此來獲得瀏覽器快取帶來的好處
- 針對Cache busting,使用指紋值(或hash值)和版本號的方式
不推薦
- 使用html元標籤去指定快取的行為
- 使用查詢引數的方式實現Cache busting
FAQ
如何知道這個檔案時來自於快取中
開啟web開發者工具,在chrome中,這個資訊會顯示在network皮膚中的Size列
如何阻止去快取一個檔案
使用下面的響應頭
Cache-Control: no-cache, no-store, must-revalidate
複製程式碼
注意
從chrome 66版本開始,chrome已經刪除了chrome://cache,原因可以參考reason1, reason2