1. 前言
瀏覽器快取 是瀏覽器將使用者請求過的靜態資源(html、css、js),儲存到電腦本地磁碟中,當瀏覽器再次訪問時,就可以直接從本地載入了,不需要再去服務端請求了。
但也不是說快取沒有缺點,如果處理不當,可能會導致服務端程式碼更新了,但是使用者卻還是老頁面。所以前端們要針對專案中各個資源的實際情況,做出合理的快取策略。
快取的優點:
- 減少了冗餘的資料傳輸,節省網費
- 減少伺服器的負擔,提升網站效能
- 加快了客戶端載入網頁的速度
2. 快取流程
這裡先介紹一下瀏覽器快取資源的一個大概的流程。
我們可以認為,瀏覽器裡有一個專門存放快取規則的一個資料庫,也可以說是一個對映表,把快取資源資訊,同電腦磁碟中的實際檔案的地址,對應起來。(大概意思,別較真)

而這個快取規則的表,在瀏覽器中是可以看到的: chrome://cache/
不過我升級了瀏覽器之後,就不好使了,但是找到了 chrome://net-internals/#httpCache ,不知道是不是就是原來的,知道的同學也可以反饋一下

瀏覽器第一次請求資源時

上面所說的 快取規則,就是宣告所請求的這個資源,要採取哪種快取策略?快取多長時間?等等。。。而這個規則,是在http的header中的返回來的。

注意: 是response header ,而不是 request header !!!
而實際上, request header 中也會攜帶規則資訊,下面會講,要區分 request 和 response
3. 快取規則
強快取和協商快取。
強快取
簡單粗暴,如果資源沒過期,就取快取,如果過期了,則請求伺服器。
如何判斷資源是否過期呢,也就是說強快取的規則怎麼看?

主要是看 response headers 中的 Cache-Control 的值,圖中的max-age = 31xxxxxxx,就是說在這些秒內,都直接使用快取,超過了就繼續請求伺服器
而和 Cache-Control 並列的,還有一個 Expires ,已經基本淘汰了,所以不用管
Cache-Control 的幾個取值含義:
private: 僅瀏覽器可以快取
public: 瀏覽器和代理伺服器都可以快取(對於private和public,前端可以認為一樣,不用深究)
max-age=xxx 過期時間(重要)
no-cache 不進行強快取(重要)
no-store 不強快取,也不協商快取,基本不用,快取越多才越好呢
注意:規則可以同時多個

所以,對於強快取,我們主要研究 Cache-Control 中的 max-age 和 no-cache
所以,判斷該資源是否命中強快取,就看 response 中 Cache-Control 的值,如果有max-age=xxx秒,則命中強快取。如果Cache-Control的值是no-cache,說明沒命中強快取,走協商快取。
強快取流程:


所以強快取步驟已經很清晰了:
- 第一次請求 a.js ,快取表中沒該資訊,直接請求後端伺服器。
- 後端伺服器返回了 a.js ,且 http response header 中 cache-control 為 max-age=xxxx,所以是強快取規則,存入快取表中。
- 第二次請求 a.js ,快取表中是 max-age, 那麼命中強快取,然後判斷是否過期,如果沒過期,直接讀快取的a.js,如果過期了,則執行協商快取的步驟了。
協商快取
觸發條件:
- Cache-Control 的值為 no-cache (不強快取)
- 或者 max-age 過期了 (強快取,但總有過期的時候)
也就是說,不管怎樣,都可能最後要進行協商快取(no-store除外)

這個圖,雖然強快取命中,但是也有 ETag 和 Last-Modified ,這兩個就是協商快取的相關規則。雖然之前的強快取流程和他倆沒關。。。
ETag:每個檔案有一個,改動檔案了就變了,可以看似md5
Last-Modified:檔案的修改時間
也就是說,每次http返回來 response header 中的 ETag和 Last-Modified,在下次請求時在 request header 就把這兩個帶上(但是名字變了ETag-->If-None-Match,Last-Modified-->If-Modified-Since ),服務端把你帶過來的標識,資源目前的標識,進行對比,然後判斷資源是否更改了。
這個過程是迴圈往復的,即快取表在每次請求成功後都會更新規則。
1. 第n次請求成功時:

2. 快取表中更新該資源的 ETag 值
3. 第n+1次請求:
從快取表中取該資源最新的ETag,然後加在 request header 中, 注意變名字了,由 ETag -- > If-None-Match

圖:


所以協商快取步驟總結:
- 請求資源時,把使用者本地該資源的 ETag 同時帶到服務端,服務端和最新資源做對比。
- 如果資源沒更改,返回304,瀏覽器讀取本地快取。
- 如果資源有更改,返回200,返回最新的資源。
4. 快取命中顯示
- 從伺服器獲取新的資源

- 命中強快取,且資源沒過期,直接讀取本地快取

- 命中協商快取,且資源未更改,讀取本地快取

注意:協商快取無論如果,都要向服務端發請求的,只不過,資源未更改時,返回的只是header資訊,所以size很小;而資源有更改時,還要返回body資料,所以size會大。
7. 其他
0. 怎麼配置資源的快取規則
可以有後端伺服器配置,也可以在nginx中配置,稍後會更新一張nginx的配置


1. 為什麼要有Etag
你可能會覺得使用Last-Modified已經足以讓瀏覽器知道本地的快取副本是否足夠新,為什麼還需要Etag呢?HTTP1.1中Etag的出現(也就是說,ETag是新增的,為了解決之前只有If-Modified的缺點)主要是為了解決幾個Last-Modified比較難解決的問題:
-
一些檔案也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個檔案被修改了,而重新GET;
-
某些檔案修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
-
某些伺服器不能精確的得到檔案的最後修改時間。
2. 強快取與協商快取的區別可以用下表來表示:

3. 使用者行為對快取的影響

即:F5 會 跳過強快取規則,直接走協商快取;;;Ctrl+F5 ,跳過所有快取規則,和第一次請求一樣,重新獲取資源
6. 總結
借兩個圖

