圖解:徹底弄懂HTTP緩存機(jī)制及原理
前言
HTTP 的緩存機(jī)制,可以說(shuō)這是前端工程師需要掌握的重要知識(shí)點(diǎn)之一。本文將針對(duì) HTTP 緩存整體的流程做一個(gè)詳細(xì)的講解,爭(zhēng)取做到大家讀完整篇文章后,對(duì)緩存有一個(gè)整體的了解。

強(qiáng)緩存
不需要發(fā)送請(qǐng)求到服務(wù)端,直接讀取瀏覽器本地緩存,在 Chrome 的 Network 中顯示的 HTTP 狀態(tài)碼是 200 ,在 Chrome 中,強(qiáng)緩存又分為 Disk Cache(存放在硬盤中)和 Memory Cache(存放在內(nèi)存中),存放的位置是由瀏覽器控制的。是否強(qiáng)緩存由 Expires、Cache-Control 和 Pragma 3 個(gè) Header 屬性共同來(lái)控制。
○ Expires
Expires 的值是一個(gè) HTTP 日期,在瀏覽器發(fā)起請(qǐng)求時(shí),會(huì)根據(jù)系統(tǒng)時(shí)間和 Expires 的值進(jìn)行比較,如果系統(tǒng)時(shí)間超過(guò)了 Expires 的值,緩存失效。由于和系統(tǒng)時(shí)間進(jìn)行比較,所以當(dāng)系統(tǒng)時(shí)間和服務(wù)器時(shí)間不一致的時(shí)候,會(huì)有緩存有效期不準(zhǔn)的問(wèn)題。Expires 的優(yōu)先級(jí)在三個(gè) Header 屬性中是最低的。
○ Cache-Control
Cache-Control 是 HTTP/1.1 中新增的屬性,在請(qǐng)求頭和響應(yīng)頭中都可以使用,常用的屬性值如有:
max-age:?jiǎn)挝皇敲?,緩存時(shí)間計(jì)算的方式是距離發(fā)起的時(shí)間的秒數(shù),超過(guò)間隔的秒數(shù)緩存失效 no-cache:不使用強(qiáng)緩存,需要與服務(wù)器驗(yàn)證緩存是否新鮮 no-store:禁止使用緩存(包括協(xié)商緩存),每次都向服務(wù)器請(qǐng)求最新的資源 private:專用于個(gè)人的緩存,中間代理、CDN 等不能緩存此響應(yīng) public:響應(yīng)可以被中間代理、CDN 等緩存 must-revalidate:在緩存過(guò)期前可以使用,過(guò)期后必須向服務(wù)器驗(yàn)證
○ Pragma
Pragma 只有一個(gè)屬性值,就是 no-cache ,效果和 Cache-Control 中的 no-cache 一致,不使用強(qiáng)緩存,需要與服務(wù)器驗(yàn)證緩存是否新鮮,在 3 個(gè)頭部屬性中的優(yōu)先級(jí)最高。
const express = require('express');
const app = express();
var options = {
etag: false, // 禁用協(xié)商緩存
lastModified: false, // 禁用協(xié)商緩存
setHeaders: (res, path, stat) => {
res.set('Cache-Control', 'max-age=10'); // 強(qiáng)緩存超時(shí)時(shí)間為10秒
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3000);
第一次加載,頁(yè)面會(huì)向服務(wù)器請(qǐng)求數(shù)據(jù),并在 Response Header 中添加 Cache-Control ,過(guò)期時(shí)間為 10 秒。




協(xié)商緩存
當(dāng)瀏覽器的強(qiáng)緩存失效的時(shí)候或者請(qǐng)求頭中設(shè)置了不走強(qiáng)緩存,并且在請(qǐng)求頭中設(shè)置了If-Modified-Since 或者 If-None-Match 的時(shí)候,會(huì)將這兩個(gè)屬性值到服務(wù)端去驗(yàn)證是否命中協(xié)商緩存,如果命中了協(xié)商緩存,會(huì)返回 304 狀態(tài),加載瀏覽器緩存,并且響應(yīng)頭會(huì)設(shè)置 Last-Modified 或者 ETag 屬性。
○ ETag/If-None-Match
ETag/If-None-Match 的值是一串 hash 碼,代表的是一個(gè)資源的標(biāo)識(shí)符,當(dāng)服務(wù)端的文件變化的時(shí)候,它的 hash碼會(huì)隨之改變,通過(guò)請(qǐng)求頭中的 If-None-Match 和當(dāng)前文件的 hash 值進(jìn)行比較,如果相等則表示命中協(xié)商緩存。ETag 又有強(qiáng)弱校驗(yàn)之分,如果 hash 碼是以 "W/" 開頭的一串字符串,說(shuō)明此時(shí)協(xié)商緩存的校驗(yàn)是弱校驗(yàn)的,只有服務(wù)器上的文件差異(根據(jù) ETag 計(jì)算方式來(lái)決定)達(dá)到能夠觸發(fā) hash 值后綴變化的時(shí)候,才會(huì)真正地請(qǐng)求資源,否則返回 304 并加載瀏覽器緩存。
○ Last-Modified/If-Modified-Since
Last-Modified/If-Modified-Since 的值代表的是文件的最后修改時(shí)間,第一次請(qǐng)求服務(wù)端會(huì)把資源的最后修改時(shí)間放到 Last-Modified 響應(yīng)頭中,第二次發(fā)起請(qǐng)求的時(shí)候,請(qǐng)求頭會(huì)帶上上一次響應(yīng)頭中的 Last-Modified 的時(shí)間,并放到 If-Modified-Since 請(qǐng)求頭屬性中,服務(wù)端根據(jù)文件最后一次修改時(shí)間和 If-Modified-Since 的值進(jìn)行比較,如果相等,返回 304 ,并加載瀏覽器緩存。
const express = require('express');
const app = express();
var options = {
etag: true, // 開啟協(xié)商緩存
lastModified: true, // 開啟協(xié)商緩存
setHeaders: (res, path, stat) => {
res.set({
'Cache-Control': 'max-age=00', // 瀏覽器不走強(qiáng)緩存
'Pragma': 'no-cache', // 瀏覽器不走強(qiáng)緩存
});
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3001);
第一次請(qǐng)求資源:


我們?cè)賮?lái)驗(yàn)證一下 ETag 在強(qiáng)校驗(yàn)的情況下,只增加一行空格,hash 值如何變化,在代碼中,我采用的是對(duì)文件進(jìn)行 MD5 加密來(lái)計(jì)算其 hash 值。
注:只是為了演示用,實(shí)際計(jì)算不是通過(guò) MD5 加密的,Apache 默認(rèn)通過(guò) FileEtag 中 FileEtag INode Mtime Size 的配置自動(dòng)生成 ETag,用戶可以通過(guò)自定義的方式來(lái)修改文件生成 ETag 的方式。
const express = require('express');
const CryptoJS = require('crypto-js/crypto-js');
const fs = require('fs');
const app = express();
var options = {
etag: true, // 只通過(guò)Etag來(lái)判斷
lastModified: false, // 關(guān)閉另一種協(xié)商緩存
setHeaders: (res, path, stat) => {
const data = fs.readFileSync(path, 'utf-8'); // 讀取文件
const hash = CryptoJS.MD5((JSON.stringify(data))); // MD5加密
res.set({
'Cache-Control': 'max-age=00', // 瀏覽器不走強(qiáng)緩存
'Pragma': 'no-cache', // 瀏覽器不走強(qiáng)緩存
'ETag': hash, // 手動(dòng)設(shè)置Etag值為MD5加密后的hash值
});
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(4000); // 使用新端口號(hào),否則上面驗(yàn)證的協(xié)商緩存會(huì)一直存在
第一次和第二次請(qǐng)求如下:



ETag/If-None-Match 的出現(xiàn)主要解決了 Last-Modified/If-Modified-Since 所解決不了的問(wèn)題:
如果文件的修改頻率在秒級(jí)以下,Last-Modified/If-Modified-Since 會(huì)錯(cuò)誤地返回 304 如果文件被修改了,但是內(nèi)容沒有任何變化的時(shí)候,Last-Modified/If-Modified-Since 會(huì)錯(cuò)誤地返回 304,上面的例子就說(shuō)明了這個(gè)問(wèn)題
總結(jié)
在實(shí)際使用場(chǎng)景中,比如政采云的官網(wǎng)。圖片、不常變化的 JS 等靜態(tài)資源都會(huì)使用緩存來(lái)提高頁(yè)面的加載速度。例如政采云首頁(yè)的頂部導(dǎo)航欄,埋點(diǎn) SDK 等等。

最后
如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

