深入淺出從根上理解 HTTP 緩存機(jī)制及原理!
HTTP 緩存,對于前端的性能優(yōu)化方面來講,是非常關(guān)鍵的,從緩存中讀取數(shù)據(jù)和直接向服務(wù)器請求數(shù)據(jù),完全就是一個在天上,一個在地下。
我們最熟悉的是 HTTP 服務(wù)器響應(yīng)返回狀態(tài)碼 304,304 代表表示告訴瀏覽器,本地有緩存數(shù)據(jù),可直接從本地獲取,無需從服務(wù)器獲取浪費(fèi)時間。
至于為什么被緩存,如何命中緩存以及緩存什么時候生效的,我們卻很少在實(shí)際開發(fā)中去了解。今天小鹿借助動畫形式來從根上理解 HTTP 緩存機(jī)制及原理。
為什么會有緩存?
單純的從計算機(jī)角度去說,比較抽象,咱們看一個實(shí)際的例子。比如,我們通常喜歡把沒看完的書放在書架上,而看完以及沒有看的書放在箱子中保存。
如果我們把所有的書保存在箱子中,每次看書都要去箱子中找,所以非常麻煩和耗時(這里的箱子,可以想象成服務(wù)器)。
當(dāng)我們開始看新書時,第一次從箱子中取出,看了一半,然后我們直接放到書架上,當(dāng)下次再看書的時候,直接從書架中取出,這里的書架,就是我們下邊要講到的緩存(一個緩存?zhèn)}庫)。
緩存的“龜”則
當(dāng)瀏覽器發(fā)出請求到數(shù)據(jù)請求回來的過程,就像是上述中的取書過程。
瀏覽器在加載資源時,根據(jù)請求頭的Expires 和 Cache-control 判斷是否命中強(qiáng)緩存,是則直接從緩存讀取資源,不會發(fā)請求到服務(wù)器。
如果沒有命中強(qiáng)緩存,瀏覽器一定會發(fā)送一個請求到服務(wù)器,通過 Last-Modified 和 Etag 驗證資源是否命中協(xié)商緩存,如果命中,服務(wù)器會將這個請求返回,但是不會返回這個資源的數(shù)據(jù),依然是從緩存中讀取資源。
如果前面兩者都沒有命中,直接從服務(wù)器加載資源。
動畫演示

HTTP 緩存分類
上述講到,HTTP 是有“龜”則的,根據(jù)瀏覽器是否向服務(wù)器發(fā)起請求來分為強(qiáng)緩存和協(xié)商緩存。
1. 強(qiáng)緩存
強(qiáng)緩存的意思就是不向服務(wù)器發(fā)起請求的緩存,也就是本地強(qiáng)制緩存。瀏覽器想要獲取特定數(shù)據(jù)的時候,首先會檢查一下本地的緩存是否存在該數(shù)據(jù),如果存在,就直接在本地獲取了,如果不存在,就向服務(wù)器所要該數(shù)據(jù)。
詳細(xì)請求過程如下動畫所示:


那么問題來了,如果我們想使用強(qiáng)緩存,那怎么判斷緩存數(shù)據(jù)什么時候失效呢?
當(dāng)瀏覽器向服務(wù)器請求數(shù)據(jù)的時候,服務(wù)器會將數(shù)據(jù)和緩存的規(guī)則返回,在響應(yīng)頭的 header 中,有兩個字段 Expires和Cache-Control。
(1) Expires
- expires: Wed, 11 Sep 2019 16:12:18 GMT
在響應(yīng)頭中 Expires 字段的意思是,當(dāng)前返回數(shù)據(jù)的緩存到期時間戳。當(dāng)瀏覽器在進(jìn)行請求的時候,會那瀏覽器本地的時候和這個時間做對比,判斷資源是否過期。
但是上述存在一個問題就是,如果我手動改變了電腦的時間,那么就會出現(xiàn)問題,這也是 HTTP1.0 中存在的問題。
(2) Cache-Control
為了解決這個問題,在 HTTP1.1 中增加了 Cache-Control 這個字段。
- Cache-Control:max-age=7200
服務(wù)器和客戶端說,這個資源緩存只可以存在 7200 秒,在這個時間段之內(nèi),你就可以在緩存獲取資源。
如果 Expire 和 Cache-control 兩者同時出現(xiàn),則以 Cache-control 為主
除此之外,cache-control 還有其他字段可以使用。
- cache-control: max-age=3600, s-maxage=31536000
- Public:只要為資源設(shè)置了 public,那么它既可以被瀏覽器緩存,也可以被代理服務(wù)器緩存;
- Private(默認(rèn)值):則該資源只能被瀏覽器緩存。
- no-store:不使用任何緩存,直接向服務(wù)器發(fā)起請求。
- no-cache:繞開瀏覽器緩存(每次發(fā)起請求不會詢問瀏覽器緩存),而是直接向服務(wù)器確認(rèn)該緩存是夠過期。
2. 協(xié)商緩存
瀏覽器第一次請求數(shù)據(jù)時,服務(wù)器會將緩存標(biāo)識與數(shù)據(jù)一起返回給客戶端,客戶端將二者備份至緩存數(shù)據(jù)庫中。
再次請求數(shù)據(jù)時,客戶端將備份的緩存標(biāo)識發(fā)送給服務(wù)器,服務(wù)器根據(jù)緩存標(biāo)識進(jìn)行判斷,判斷成功后,返回304狀態(tài)碼,通知客戶端比較成功,可以使用緩存數(shù)據(jù)。


- // 命中緩存的響應(yīng)字段
- Request Method:GET
- Status Code: 304 Not Modified
怎么來識別協(xié)商緩存的?主要通過報文頭部 header 中的Last-Modified,If-Modified-Since 以及ETag、If-None-Match 字段來進(jìn)行識別。
(1) Last-Modified
Last-Modified 字段的意思是服務(wù)器資源的最后修改時間。第一次請求服務(wù)器,服務(wù)器的頭部字段可增加這個字段,用于設(shè)置協(xié)商緩存。
- Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
當(dāng)瀏覽器再次發(fā)起請求的時候,首部字段增加 If-Modified-Since 本地時間戳字段發(fā)給服務(wù)器。
- If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
服務(wù)端接收到請求之后,就拿 If-Modified-Since 字段值和本身的過期時間對比。
如果請求頭中的這個值小于最后修改時間,返回的 304 響應(yīng),讓其在本地瀏覽器緩存取出數(shù)據(jù)。如果時間過期,并在 Response Headers中添加新的 Last-Modified 值返回給瀏覽器。
但是 Last-Modified 存在一個局限性,有以下兩種情況:
- 不該請求,還會請求。編輯了文件,文件內(nèi)容沒有變,但是服務(wù)器確認(rèn)為我們改動了文件,所以重新設(shè)置了緩存時間,當(dāng)做新請求返回給瀏覽器。
- 該請求,反而沒有請求。修改文件速度很快,快過 If-Modified-Since 字段時間差的檢測,文件雖然改動了,但是并沒有重新生成新的資源。
(2) ETag
ETag 代表的意思是標(biāo)識字符串。由于上述 Last-Modified 字段存在的缺陷,所以在 HTTP / 1.1 我們對資源進(jìn)行內(nèi)容編碼,只要內(nèi)容被改變,這個編碼就不同。
和上述請求原理一樣,瀏覽器首次發(fā)起請求,然后服務(wù)器在響應(yīng)頭返回一個標(biāo)識字符串。
- ETag: W/"2a3b-1602480f459"
瀏覽器再次發(fā)起請求,攜帶一個值相同的字符串。
- If-None-Match: W/"2a3b-1602480f459"
服務(wù)端接收到該字符串就會作對比,如果相同,則讓其讀取本地緩存,否則,將新的資源返回給瀏覽器端。
緩存位置
緩存的位置按照獲取資源請求優(yōu)先級,緩存位置依次如下:
- Memory Cache(內(nèi)存緩存)
- Service Worker(離線緩存)
- Disk Cache(磁盤緩存)
- Push Cache(推送緩存)
(1) Memory Cache
Memory 為內(nèi)存緩存,是瀏覽器最先嘗試命中的緩存,也是響應(yīng)最快的緩存。但是存活時間最短的,當(dāng)進(jìn)程結(jié)束后,tab 標(biāo)簽關(guān)閉后,緩存就不存在了。
因為內(nèi)存空間比較小,通常較小的資源放在內(nèi)存緩存中,比如 base64 圖片等資源。
(2) Service Worker
Service Worker 是一種獨(dú)立于主線程之外的 Javascript 線程。它脫離于瀏覽器窗體,因此無法直接訪問 DOM。
可以幫我們實(shí)現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能。
(3) Disk Cache
內(nèi)存的優(yōu)先性,導(dǎo)致大文件不能緩存到內(nèi)存中,那么磁盤緩存則不同。雖然存儲效率比內(nèi)存緩存慢,但是存儲容量和存儲市場有優(yōu)勢。
(4) Push Cache
它是最后一道緩存命中,屬于 HTTP2 的內(nèi)容。如果感興趣的同學(xué),可以先去了解了解。