徹底理解瀏覽器的Http緩存機(jī)制

作者丨saucxs
https://www.mwcxs.top/page/565.html
概述
瀏覽器的緩存機(jī)制也就是我們說(shuō)的HTTP緩存機(jī)制,其機(jī)制是根據(jù)HTTP報(bào)文的緩存標(biāo)識(shí)進(jìn)行的,所以在分析瀏覽器緩存機(jī)制之前,我們先使用圖文簡(jiǎn)單介紹一下HTTP報(bào)文,HTTP報(bào)文分為兩種:HTTP請(qǐng)求(Request)報(bào)文,報(bào)文格式為:請(qǐng)求行 – HTTP頭(通用信息頭,請(qǐng)求頭,實(shí)體頭) – 請(qǐng)求報(bào)文主體(只有POST才有報(bào)文主體),如下圖


HTTP響應(yīng)(Response)報(bào)文,報(bào)文格式為:狀態(tài)行 – HTTP頭(通用信息頭,響應(yīng)頭,實(shí)體頭) – 響應(yīng)報(bào)文主體,如下圖


注:通用信息頭指的是請(qǐng)求和響應(yīng)報(bào)文都支持的頭域,分別為Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via;實(shí)體頭則是實(shí)體信息的實(shí)體頭域,分別為Allow、Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、extension-header。這里只是為了方便理解,將通用信息頭,響應(yīng)頭/請(qǐng)求頭,實(shí)體頭都?xì)w為了HTTP頭。以上的概念在這里我們不做多講解,只簡(jiǎn)單介紹,有興趣的童鞋可以自行研究。
緩存過(guò)程分析
瀏覽器與服務(wù)器通信的方式為應(yīng)答模式,即是:瀏覽器發(fā)起HTTP請(qǐng)求 – 服務(wù)器響應(yīng)該請(qǐng)求。那么瀏覽器第一次向服務(wù)器發(fā)起該請(qǐng)求后拿到請(qǐng)求結(jié)果,會(huì)根據(jù)響應(yīng)報(bào)文中HTTP頭的緩存標(biāo)識(shí),決定是否緩存結(jié)果,是則將請(qǐng)求結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中,簡(jiǎn)單的過(guò)程如下圖:

由上圖我們可以知道:
瀏覽器每次發(fā)起請(qǐng)求,都會(huì)先在瀏覽器緩存中查找該請(qǐng)求的結(jié)果以及緩存標(biāo)識(shí)
瀏覽器每次拿到返回的請(qǐng)求結(jié)果都會(huì)將該結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中
以上兩點(diǎn)結(jié)論就是瀏覽器緩存機(jī)制的關(guān)鍵,他確保了每個(gè)請(qǐng)求的緩存存入與讀取,只要我們?cè)倮斫鉃g覽器緩存的使用規(guī)則,那么所有的問(wèn)題就迎刃而解了,本文也將圍繞著這點(diǎn)進(jìn)行詳細(xì)分析。為了方便大家理解,這里我們根據(jù)是否需要向服務(wù)器重新發(fā)起HTTP請(qǐng)求將緩存過(guò)程分為兩個(gè)部分,分別是強(qiáng)制緩存和協(xié)商緩存。
強(qiáng)制緩存
強(qiáng)制緩存就是向?yàn)g覽器緩存查找該請(qǐng)求結(jié)果,并根據(jù)該結(jié)果的緩存規(guī)則來(lái)決定是否使用該緩存結(jié)果的過(guò)程,強(qiáng)制緩存的情況主要有三種(暫不分析協(xié)商緩存過(guò)程),如下:不存在該緩存結(jié)果和緩存標(biāo)識(shí),強(qiáng)制緩存失效,則直接向服務(wù)器發(fā)起請(qǐng)求(跟第一次發(fā)起請(qǐng)求一致),如下圖:

存在該緩存結(jié)果和緩存標(biāo)識(shí),但該結(jié)果已失效,強(qiáng)制緩存失效,則使用協(xié)商緩存(暫不分析),如下圖

存在該緩存結(jié)果和緩存標(biāo)識(shí),且該結(jié)果尚未失效,強(qiáng)制緩存生效,直接返回該結(jié)果,如下圖

那么強(qiáng)制緩存的緩存規(guī)則是什么?
當(dāng)瀏覽器向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)器會(huì)將緩存規(guī)則放入HTTP響應(yīng)報(bào)文的HTTP頭中和請(qǐng)求結(jié)果一起返回給瀏覽器,控制強(qiáng)制緩存的字段分別是Expires和Cache-Control,其中Cache-Control優(yōu)先級(jí)比Expires高。
Expires
Expires是HTTP/1.0控制網(wǎng)頁(yè)緩存的字段,其值為服務(wù)器返回該請(qǐng)求結(jié)果緩存的到期時(shí)間,即再次發(fā)起該請(qǐng)求時(shí),如果客戶端的時(shí)間小于Expires的值時(shí),直接使用緩存結(jié)果。
Expires是HTTP/1.0的字段,但是現(xiàn)在瀏覽器默認(rèn)使用的是HTTP/1.1,那么在HTTP/1.1中網(wǎng)頁(yè)緩存還是否由Expires控制?
到了HTTP/1.1,Expire已經(jīng)被Cache-Control替代,原因在于Expires控制緩存的原理是使用客戶端的時(shí)間與服務(wù)端返回的時(shí)間做對(duì)比,那么如果客戶端與服務(wù)端的時(shí)間因?yàn)槟承┰颍ɡ鐣r(shí)區(qū)不同;客戶端和服務(wù)端有一方的時(shí)間不準(zhǔn)確)發(fā)生誤差,那么強(qiáng)制緩存則會(huì)直接失效,這樣的話強(qiáng)制緩存的存在則毫無(wú)意義,那么Cache-Control又是如何控制的呢?
Cache-Control
在HTTP/1.1中,Cache-Control是最重要的規(guī)則,主要用于控制網(wǎng)頁(yè)緩存,主要取值為:
public:所有內(nèi)容都將被緩存(客戶端和代理服務(wù)器都可緩存)
private:所有內(nèi)容只有客戶端可以緩存,Cache-Control的默認(rèn)取值
no-cache:客戶端緩存內(nèi)容,但是是否使用緩存則需要經(jīng)過(guò)協(xié)商緩存來(lái)驗(yàn)證決定
no-store:所有內(nèi)容都不會(huì)被緩存,即不使用強(qiáng)制緩存,也不使用協(xié)商緩存
max-age=xxx (xxx is numeric):緩存內(nèi)容將在xxx秒后失效
接下來(lái),我們直接看一個(gè)例子,如下:

由上面的例子我們可以知道:
HTTP響應(yīng)報(bào)文中expires的時(shí)間值,是一個(gè)絕對(duì)值
HTTP響應(yīng)報(bào)文中Cache-Control為max-age=600,是相對(duì)值
由于Cache-Control的優(yōu)先級(jí)比expires,那么直接根據(jù)Cache-Control的值進(jìn)行緩存,意思就是說(shuō)在600秒內(nèi)再次發(fā)起該請(qǐng)求,則會(huì)直接使用緩存結(jié)果,強(qiáng)制緩存生效。注:在無(wú)法確定客戶端的時(shí)間是否與服務(wù)端的時(shí)間同步的情況下,Cache-Control相比于expires是更好的選擇,所以同時(shí)存在時(shí),只有Cache-Control生效。了解強(qiáng)制緩存的過(guò)程后,我們拓展性的思考一下:
瀏覽器的緩存存放在哪里,如何在瀏覽器中判斷強(qiáng)制緩存是否生效?

這里我們以博客的請(qǐng)求為例,狀態(tài)碼為灰色的請(qǐng)求則代表使用了強(qiáng)制緩存,請(qǐng)求對(duì)應(yīng)的Size值則代表該緩存存放的位置,分別為from memory cache 和 from disk cache。
那么from memory cache 和 from disk cache又分別代表的是什么呢?什么時(shí)候會(huì)使用from disk cache,什么時(shí)候會(huì)使用from memory cache呢?
from memory cache代表使用內(nèi)存中的緩存,from disk cache則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)閙emory –> disk。雖然我已經(jīng)直接把結(jié)論說(shuō)出來(lái)了,但是相信有不少人對(duì)此不能理解,那么接下來(lái)我們一起詳細(xì)分析一下緩存讀取問(wèn)題,這里仍讓以我的博客為例進(jìn)行分析:訪問(wèn)https://heyingye.github.io/–> 200 –> 關(guān)閉博客的標(biāo)簽頁(yè) –> 重新打開(kāi)https://heyingye.github.io/–> 200(from disk cache) –> 刷新 –> 200(from memory cache)過(guò)程如下:
訪問(wèn)https://heyingye.github.io/

關(guān)閉博客的標(biāo)簽頁(yè)
重新打開(kāi)https://heyingye.github.io/

刷新

看到這里可能有人小伙伴問(wèn)了,最后一個(gè)步驟刷新的時(shí)候,不是同時(shí)存在著from disk cache和from memory cache嗎?
對(duì)于這個(gè)問(wèn)題,我們需要了解內(nèi)存緩存(from memory cache)和硬盤緩存(from disk cache),如下:
內(nèi)存緩存(from memory cache):內(nèi)存緩存具有兩個(gè)特點(diǎn),分別是快速讀取和時(shí)效性:
快速讀取:內(nèi)存緩存會(huì)將編譯解析后的文件,直接存入該進(jìn)程的內(nèi)存中,占據(jù)該進(jìn)程一定的內(nèi)存資源,以方便下次運(yùn)行使用時(shí)的快速讀取。
時(shí)效性:一旦該進(jìn)程關(guān)閉,則該進(jìn)程的內(nèi)存則會(huì)清空。
硬盤緩存(from disk cache):硬盤緩存則是直接將緩存寫(xiě)入硬盤文件中,讀取緩存需要對(duì)該緩存存放的硬盤文件進(jìn)行I/O操作,然后重新解析該緩存內(nèi)容,讀取復(fù)雜,速度比內(nèi)存緩存慢。
在瀏覽器中,瀏覽器會(huì)在js和圖片等文件解析執(zhí)行后直接存入內(nèi)存緩存中,那么當(dāng)刷新頁(yè)面時(shí)只需直接從內(nèi)存緩存中讀取(from memory cache);而css文件則會(huì)存入硬盤文件中,所以每次渲染頁(yè)面都需要從硬盤讀取緩存(from disk cache)。
協(xié)商緩存
協(xié)商緩存就是強(qiáng)制緩存失效后,瀏覽器攜帶緩存標(biāo)識(shí)向服務(wù)器發(fā)起請(qǐng)求,由服務(wù)器根據(jù)緩存標(biāo)識(shí)決定是否使用緩存的過(guò)程,主要有以下兩種情況:協(xié)商緩存生效,返回304,如下

協(xié)商緩存失效,返回200和請(qǐng)求結(jié)果結(jié)果,如下

同樣,協(xié)商緩存的標(biāo)識(shí)也是在響應(yīng)報(bào)文的HTTP頭中和請(qǐng)求結(jié)果一起返回給瀏覽器的,控制協(xié)商緩存的字段分別有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的優(yōu)先級(jí)比Last-Modified / If-Modified-Since高。
Last-Modified / If-Modified-Since
Last-Modified是服務(wù)器響應(yīng)請(qǐng)求時(shí),返回該資源文件在服務(wù)器最后被修改的時(shí)間,如下。

If-Modified-Since則是客戶端再次發(fā)起該請(qǐng)求時(shí),攜帶上次請(qǐng)求返回的Last-Modified值,通過(guò)此字段值告訴服務(wù)器該資源上次請(qǐng)求返回的最后被修改時(shí)間。服務(wù)器收到該請(qǐng)求,發(fā)現(xiàn)請(qǐng)求頭含有If-Modified-Since字段,則會(huì)根據(jù)If-Modified-Since的字段值與該資源在服務(wù)器的最后被修改時(shí)間做對(duì)比,若服務(wù)器的資源最后被修改時(shí)間大于If-Modified-Since的字段值,則重新返回資源,狀態(tài)碼為200;否則則返回304,代表資源無(wú)更新,可繼續(xù)使用緩存文件,如下。

Etag / If-None-Match
Etag是服務(wù)器響應(yīng)請(qǐng)求時(shí),返回當(dāng)前資源文件的一個(gè)唯一標(biāo)識(shí)(由服務(wù)器生成),如下。

If-None-Match是客戶端再次發(fā)起該請(qǐng)求時(shí),攜帶上次請(qǐng)求返回的唯一標(biāo)識(shí)Etag值,通過(guò)此字段值告訴服務(wù)器該資源上次請(qǐng)求返回的唯一標(biāo)識(shí)值。服務(wù)器收到該請(qǐng)求后,發(fā)現(xiàn)該請(qǐng)求頭中含有If-None-Match,則會(huì)根據(jù)If-None-Match的字段值與該資源在服務(wù)器的Etag值做對(duì)比,一致則返回304,代表資源無(wú)更新,繼續(xù)使用緩存文件;不一致則重新返回資源文件,狀態(tài)碼為200,如下。

注:Etag / If-None-Match優(yōu)先級(jí)高于Last-Modified / If-Modified-Since,同時(shí)存在則只有Etag / If-None-Match生效。
總結(jié)
強(qiáng)制緩存優(yōu)先于協(xié)商緩存進(jìn)行,若強(qiáng)制緩存(Expires和Cache-Control)生效則直接使用緩存,若不生效則進(jìn)行協(xié)商緩存(Last-Modified / If-Modified-Since和Etag / If-None-Match),協(xié)商緩存由服務(wù)器決定是否使用緩存,若協(xié)商緩存失效,那么代表該請(qǐng)求的緩存失效,重新獲取請(qǐng)求結(jié)果,再存入瀏覽器緩存中;生效則返回304,繼續(xù)使用緩存,主要過(guò)程如下:

專注分享當(dāng)下最實(shí)用的前端技術(shù)。關(guān)注前端達(dá)人,與達(dá)人一起學(xué)習(xí)進(jìn)步!
長(zhǎng)按關(guān)注"前端達(dá)人"

