日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一文讀懂前端緩存

大家都知道緩存的英文叫做 cache。但我發(fā)現(xiàn)一個(gè)有趣的現(xiàn)象:這個(gè)單詞在不同人的口中有不同的讀音。為了全面了解緩存,我們得先從讀音開始,這樣才能夠在和其他同事(例如 PM)交(zhuāng)流(bī)時(shí)體現(xiàn)自己的修(bī)養(yǎng)(gé)。

cache 怎么念

在國外 IT 圈和大部分國外視頻中,cache 的發(fā)音是 /k??/(同 cash),這也是一個(gè)廣泛認(rèn)可的發(fā)音。但我發(fā)現(xiàn)在中國的 IT 圈還有相當(dāng)一部分程序員(比如我自己……)讀作 /k?t?/(同 catch)。雖然不太正確,但并不妨礙互相交流。(不過為了純正,還是應(yīng)該向正確的方向靠攏)

此外還有一些小眾的讀法,例如 /ke??/(同 kaysh),甚至 /k??e?/(像個(gè)法語發(fā)音,重音在后面)。這些因?yàn)樘”娏?,可能?huì)引起溝通障礙,估計(jì)只有在特定場(chǎng)合或者特定圈子才能順暢使用。

前端緩存/后端緩存

扯了些沒用的,我們先進(jìn)入定義環(huán)節(jié):什么是前端緩存?與之相對(duì)的什么又是后端緩存?

基本的網(wǎng)絡(luò)請(qǐng)求就是三個(gè)步驟:請(qǐng)求,處理,響應(yīng)。

后端緩存主要集中于“處理”步驟,通過保留數(shù)據(jù)庫連接,存儲(chǔ)處理結(jié)果等方式縮短處理時(shí)間,盡快進(jìn)入“響應(yīng)”步驟。當(dāng)然這不在本文的討論范圍之內(nèi)。

而前端緩存則可以在剩下的兩步:“請(qǐng)求”和“響應(yīng)”中進(jìn)行。在“請(qǐng)求”步驟中,瀏覽器也可以通過存儲(chǔ)結(jié)果的方式直接使用資源,直接省去了發(fā)送請(qǐng)求;而“響應(yīng)”步驟需要瀏覽器和服務(wù)器共同配合,通過減少響應(yīng)內(nèi)容來縮短傳輸時(shí)間。這些都會(huì)在下面進(jìn)行討論。

本文主要包含

  • 按緩存位置分類 (memory cache, disk cache, Service Worker 等)
  • 按失效策略分類 (Cache-Control, ETag 等)
  • 幫助理解原理的一些案例
  • 緩存的應(yīng)用模式

按緩存位置分類

我看過的大部分討論緩存的文章會(huì)直接從 HTTP 協(xié)議頭中的緩存字段開始,例如 Cache-Control, ETag, max-age 等。但偶爾也會(huì)聽到別人討論 memory cache, disk cache 等。那這兩種分類體系究竟有何關(guān)聯(lián)?是否有交叉?(我個(gè)人認(rèn)為這是本文的最大價(jià)值所在,因?yàn)樵趯懼拔易约阂彩潜粌煞N分類體系搞的一團(tuán)糟)

實(shí)際上,HTTP 協(xié)議頭的那些字段,都屬于 disk cache 的范疇,是幾個(gè)緩存位置的其中之一。因此本著從全局到局部的原則,我們應(yīng)當(dāng)先從緩存位置開始討論。等講到 disk cache 時(shí),才會(huì)詳細(xì)講述這些協(xié)議頭的字段及其作用。

我們可以在 Chrome 的開發(fā)者工具中,Network -> Size 一列看到一個(gè)請(qǐng)求最終的處理方式:如果是大小 (多少 K, 多少 M 等) 就表示是網(wǎng)絡(luò)請(qǐng)求,否則會(huì)列出 from memory cache, from disk cache 和 from ServiceWorker。

它們的優(yōu)先級(jí)是:(由上到下尋找,找到即返回;找不到則繼續(xù))

  • Service Worker
  • Memory Cache
  • Disk Cache
  • 網(wǎng)絡(luò)請(qǐng)求

memory cache

memory cache 是內(nèi)存中的緩存,(與之相對(duì) disk cache 就是硬盤上的緩存)。按照操作系統(tǒng)的常理:先讀內(nèi)存,再讀硬盤。disk cache 將在后面介紹 (因?yàn)樗膬?yōu)先級(jí)更低一些),這里先討論 memory cache。

幾乎所有的網(wǎng)絡(luò)請(qǐng)求資源都會(huì)被瀏覽器自動(dòng)加入到 memory cache 中。但是也正因?yàn)閿?shù)量很大但是瀏覽器占用的內(nèi)存不能無限擴(kuò)大這樣兩個(gè)因素,memory cache 注定只能是個(gè)“短期存儲(chǔ)”。常規(guī)情況下,瀏覽器的 TAB 關(guān)閉后該次瀏覽的 memory cache 便告失效 (為了給其他 TAB 騰出位置)。而如果極端情況下 (例如一個(gè)頁面的緩存就占用了超級(jí)多的內(nèi)存),那可能在 TAB 沒關(guān)閉之前,排在前面的緩存就已經(jīng)失效了。

剛才提過,幾乎所有的請(qǐng)求資源 都能進(jìn)入 memory cache,這里細(xì)分一下主要有兩塊:

preloader。如果你對(duì)這個(gè)機(jī)制不太了解,這里做一個(gè)簡(jiǎn)單的介紹,詳情可以參閱這篇文章。

熟悉瀏覽器處理流程的同學(xué)們應(yīng)該了解,在瀏覽器打開網(wǎng)頁的過程中,會(huì)先請(qǐng)求 HTML 然后解析。之后如果瀏覽器發(fā)現(xiàn)了 js, css 等需要解析和執(zhí)行的資源時(shí),它會(huì)使用 CPU 資源對(duì)它們進(jìn)行解析和執(zhí)行。在古老的年代(大約 2007 年以前),“請(qǐng)求 js/css - 解析執(zhí)行 - 請(qǐng)求下一個(gè) js/css - 解析執(zhí)行下一個(gè) js/css” 這樣的“串行”操作模式在每次打開頁面之前進(jìn)行著。很明顯在解析執(zhí)行的時(shí)候,網(wǎng)絡(luò)請(qǐng)求是空閑的,這就有了發(fā)揮的空間:我們能不能一邊解析執(zhí)行 js/css,一邊去請(qǐng)求下一個(gè)(或下一批)資源呢?

這就是 preloader 要做的事情。不過 preloader 沒有一個(gè)官方標(biāo)準(zhǔn),所以每個(gè)瀏覽器的處理都略有區(qū)別。例如有些瀏覽器還會(huì)下載 css 中的 @import 內(nèi)容或者

而這些被 preloader 請(qǐng)求夠來的資源就會(huì)被放入 memory cache 中,供之后的解析執(zhí)行操作使用。

preload (雖然看上去和剛才的 preloader 就差了倆字母)。實(shí)際上這個(gè)大家應(yīng)該更加熟悉一些,例如 。這些顯式指定的預(yù)加載資源,也會(huì)被放入 memory cache 中。

memory cache 機(jī)制保證了一個(gè)頁面中如果有兩個(gè)相同的請(qǐng)求 (例如兩個(gè) src 相同的 ,兩個(gè) href 相同的 )都實(shí)際只會(huì)被請(qǐng)求最多一次,避免浪費(fèi)。

不過在匹配緩存時(shí),除了匹配完全相同的 URL 之外,還會(huì)比對(duì)他們的類型,CORS 中的域名規(guī)則等。因此一個(gè)作為腳本 (script) 類型被緩存的資源是不能用在圖片 (image) 類型的請(qǐng)求中的,即便他們 src 相等。

在從 memory cache 獲取緩存內(nèi)容時(shí),瀏覽器會(huì)忽視例如 max-age=0, no-cache 等頭部配置。例如頁面上存在幾個(gè)相同 src 的圖片,即便它們可能被設(shè)置為不緩存,但依然會(huì)從 memory cache 中讀取。這是因?yàn)?memory cache 只是短期使用,大部分情況生命周期只有一次瀏覽而已。而 max-age=0 在語義上普遍被解讀為“不要在下次瀏覽時(shí)使用”,所以和 memory cache 并不沖突。

但如果站長(zhǎng)是真心不想讓一個(gè)資源進(jìn)入緩存,就連短期也不行,那就需要使用 no-store。存在這個(gè)頭部配置的話,即便是 memory cache 也不會(huì)存儲(chǔ),自然也不會(huì)從中讀取了。(后面的第二個(gè)示例有關(guān)于這點(diǎn)的體現(xiàn))

disk cache

disk cache 也叫 HTTP cache,顧名思義是存儲(chǔ)在硬盤上的緩存,因此它是持久存儲(chǔ)的,是實(shí)際存在于文件系統(tǒng)中的。而且它允許相同的資源在跨會(huì)話,甚至跨站點(diǎn)的情況下使用,例如兩個(gè)站點(diǎn)都使用了同一張圖片。

disk cache 會(huì)嚴(yán)格根據(jù) HTTP 頭信息中的各類字段來判定哪些資源可以緩存,哪些資源不可以緩存;哪些資源是仍然可用的,哪些資源是過時(shí)需要重新請(qǐng)求的。當(dāng)命中緩存之后,瀏覽器會(huì)從硬盤中讀取資源,雖然比起從內(nèi)存中讀取慢了一些,但比起網(wǎng)絡(luò)請(qǐng)求還是快了不少的。絕大部分的緩存都來自 disk cache。

關(guān)于 HTTP 的協(xié)議頭中的緩存字段,我們會(huì)在稍后進(jìn)行詳細(xì)討論。

凡是持久性存儲(chǔ)都會(huì)面臨容量增長(zhǎng)的問題,disk cache 也不例外。在瀏覽器自動(dòng)清理時(shí),會(huì)有神秘的算法去把“最老的”或者“最可能過時(shí)的”資源刪除,因此是一個(gè)一個(gè)刪除的。不過每個(gè)瀏覽器識(shí)別“最老的”和“最可能過時(shí)的”資源的算法不盡相同,可能也是它們差異性的體現(xiàn)。

Service Worker

上述的緩存策略以及緩存/讀取/失效的動(dòng)作都是由瀏覽器內(nèi)部判斷 & 進(jìn)行的,我們只能設(shè)置響應(yīng)頭的某些字段來告訴瀏覽器,而不能自己操作。舉個(gè)生活中去銀行存/取錢的例子來說,你只能告訴銀行職員,我要存/取多少錢,然后把由他們會(huì)經(jīng)過一系列的記錄和手續(xù)之后,把錢放到金庫中去,或者從金庫中取出錢來交給你。

但 Service Worker 的出現(xiàn),給予了我們另外一種更加靈活,更加直接的操作方式。依然以存/取錢為例,我們現(xiàn)在可以繞開銀行職員,自己走到金庫前(當(dāng)然是有別于上述金庫的一個(gè)單獨(dú)的小金庫),自己把錢放進(jìn)去或者取出來。因此我們可以選擇放哪些錢(緩存哪些文件),什么情況把錢取出來(路由匹配規(guī)則),取哪些錢出來(緩存匹配并返回)。當(dāng)然現(xiàn)實(shí)中銀行沒有給我們開放這樣的服務(wù)。

Service Worker 能夠操作的緩存是有別于瀏覽器內(nèi)部的 memory cache 或者 disk cache 的。我們可以從 Chrome 的 F12 中,Application -> Cache Storage 找到這個(gè)單獨(dú)的“小金庫”。除了位置不同之外,這個(gè)緩存是永久性的,即關(guān)閉 TAB 或者瀏覽器,下次打開依然還在(而 memory cache 不是)。有兩種情況會(huì)導(dǎo)致這個(gè)緩存中的資源被清除:手動(dòng)調(diào)用 API cache.delete(resource) 或者容量超過限制,被瀏覽器全部清空。

如果 Service Worker 沒能命中緩存,一般情況會(huì)使用 fetch() 方法繼續(xù)獲取資源。這時(shí)候,瀏覽器就去 memory cache 或者 disk cache 進(jìn)行下一次找緩存的工作了。注意:經(jīng)過 Service Worker 的 fetch() 方法獲取的資源,即便它并沒有命中 Service Worker 緩存,甚至實(shí)際走了網(wǎng)絡(luò)請(qǐng)求,也會(huì)標(biāo)注為 from ServiceWorker。這個(gè)情況在后面的第三個(gè)示例中有所體現(xiàn)。

請(qǐng)求網(wǎng)絡(luò)

如果一個(gè)請(qǐng)求在上述 3 個(gè)位置都沒有找到緩存,那么瀏覽器會(huì)正式發(fā)送網(wǎng)絡(luò)請(qǐng)求去獲取內(nèi)容。之后容易想到,為了提升之后請(qǐng)求的緩存命中率,自然要把這個(gè)資源添加到緩存中去。具體來說:

  • 根據(jù) Service Worker 中的 handler 決定是否存入 Cache Storage (額外的緩存位置)。
  • 根據(jù) HTTP 頭部的相關(guān)字段(Cache-control, Pragma 等)決定是否存入 disk cache
  • memory cache 保存一份資源 的引用,以備下次使用。

按失效策略分類

memory cache 是瀏覽器為了加快讀取緩存速度而進(jìn)行的自身的優(yōu)化行為,不受開發(fā)者控制,也不受 HTTP 協(xié)議頭的約束,算是一個(gè)黑盒。Service Worker 是由開發(fā)者編寫的額外的腳本,且緩存位置獨(dú)立,出現(xiàn)也較晚,使用還不算太廣泛。所以我們平時(shí)最為熟悉的其實(shí)是 disk cache,也叫 HTTP cache (因?yàn)椴幌?memory cache,它遵守 HTTP 協(xié)議頭中的字段)。平時(shí)所說的強(qiáng)制緩存,對(duì)比緩存,以及 Cache-Control 等,也都?xì)w于此類。

強(qiáng)制緩存 (也叫強(qiáng)緩存)

強(qiáng)制緩存的含義是,當(dāng)客戶端請(qǐng)求后,會(huì)先訪問緩存數(shù)據(jù)庫看緩存是否存在。如果存在則直接返回;不存在則請(qǐng)求真的服務(wù)器,響應(yīng)后再寫入緩存數(shù)據(jù)庫。

強(qiáng)制緩存直接減少請(qǐng)求數(shù),是提升最大的緩存策略。 它的優(yōu)化覆蓋了文章開頭提到過的請(qǐng)求數(shù)據(jù)的全部三個(gè)步驟。如果考慮使用緩存來優(yōu)化網(wǎng)頁性能的話,強(qiáng)制緩存應(yīng)該是首先被考慮的。

Expires

這是 HTTP 1.0 的字段,表示緩存到期時(shí)間,是一個(gè)絕對(duì)的時(shí)間 (當(dāng)前時(shí)間+緩存時(shí)間),如

可以造成強(qiáng)制緩存的字段是 Cache-control 和 Expires。

 
 
 
 
  1. Expires: Thu, 10 Nov 2017 08:45:11 GMT 

在響應(yīng)消息頭中,設(shè)置這個(gè)字段之后,就可以告訴瀏覽器,在未過期之前不需要再次請(qǐng)求。

但是,這個(gè)字段設(shè)置時(shí)有兩個(gè)缺點(diǎn):

  • 由于是絕對(duì)時(shí)間,用戶可能會(huì)將客戶端本地的時(shí)間進(jìn)行修改,而導(dǎo)致瀏覽器判斷緩存失效,重新請(qǐng)求該資源。此外,即使不考慮自信修改,時(shí)差或者誤差等因素也可能造成客戶端與服務(wù)端的時(shí)間不一致,致使緩存失效。
  • 寫法太復(fù)雜了。表示時(shí)間的字符串多個(gè)空格,少個(gè)字母,都會(huì)導(dǎo)致非法屬性從而設(shè)置失效。

Cache-control

已知Expires的缺點(diǎn)之后,在HTTP/1.1中,增加了一個(gè)字段Cache-control,該字段表示資源緩存的最大有效時(shí)間,在該時(shí)間內(nèi),客戶端不需要向服務(wù)器發(fā)送請(qǐng)求

這兩者的區(qū)別就是前者是絕對(duì)時(shí)間,而后者是相對(duì)時(shí)間。如下:

 
 
 
 
  1. Cache-control: max-age=2592000 

下面列舉一些 Cache-control 字段常用的值:(完整的列表可以查看 MDN)

  • max-age:即最大有效時(shí)間,在上面的例子中我們可以看到
  • must-revalidate:如果超過了 max-age 的時(shí)間,瀏覽器必須向服務(wù)器發(fā)送請(qǐng)求,驗(yàn)證資源是否還有效。
  • no-cache:雖然字面意思是“不要緩存”,但實(shí)際上還是要求客戶端緩存內(nèi)容的,只是是否使用這個(gè)內(nèi)容由后續(xù)的對(duì)比來決定。
  • no-store: 真正意義上的“不要緩存”。所有內(nèi)容都不走緩存,包括強(qiáng)制和對(duì)比。
  • public:所有的內(nèi)容都可以被緩存 (包括客戶端和代理服務(wù)器, 如 CDN)
  • private:所有的內(nèi)容只有客戶端才可以緩存,代理服務(wù)器不能緩存。默認(rèn)值。

這些值可以混合使用,例如 Cache-control:public, max-age=2592000。在混合使用時(shí),它們的優(yōu)先級(jí)如下圖:

這里有一個(gè)疑問:max-age=0 和 no-cache 等價(jià)嗎?從規(guī)范的字面意思來說,max-age 到期是 應(yīng)該(SHOULD) 重新驗(yàn)證,而 no-cache 是 必須(MUST) 重新驗(yàn)證。但實(shí)際情況以瀏覽器實(shí)現(xiàn)為準(zhǔn),大部分情況他們倆的行為還是一致的。(如果是 max-age=0, must-revalidate 就和 no-cache 等價(jià)了)

順帶一提,在 HTTP/1.1 之前,如果想使用 no-cache,通常是使用 Pragma 字段,如 Pragma: no-cache(這也是 Pragma 字段唯一的取值)。但是這個(gè)字段只是瀏覽器約定俗成的實(shí)現(xiàn),并沒有確切規(guī)范,因此缺乏可靠性。它應(yīng)該只作為一個(gè)兼容字段出現(xiàn),在當(dāng)前的網(wǎng)絡(luò)環(huán)境下其實(shí)用處已經(jīng)很小。

總結(jié)一下,自從 HTTP/1.1 開始,Expires 逐漸被 Cache-control 取代。Cache-control 是一個(gè)相對(duì)時(shí)間,即使客戶端時(shí)間發(fā)生改變,相對(duì)時(shí)間也不會(huì)隨之改變,這樣可以保持服務(wù)器和客戶端的時(shí)間一致性。而且 Cache-control 的可配置性比較強(qiáng)大。

Cache-control 的優(yōu)先級(jí)高于 Expires,為了兼容 HTTP/1.0 和 HTTP/1.1,實(shí)際項(xiàng)目中兩個(gè)字段我們都會(huì)設(shè)置。

對(duì)比緩存 (也叫協(xié)商緩存)

當(dāng)強(qiáng)制緩存失效(超過規(guī)定時(shí)間)時(shí),就需要使用對(duì)比緩存,由服務(wù)器決定緩存內(nèi)容是否失效。

流程上說,瀏覽器先請(qǐng)求緩存數(shù)據(jù)庫,返回一個(gè)緩存標(biāo)識(shí)。之后瀏覽器拿這個(gè)標(biāo)識(shí)和服務(wù)器通訊。如果緩存未失效,則返回 HTTP 狀態(tài)碼 304 表示繼續(xù)使用,于是客戶端繼續(xù)使用緩存;如果失效,則返回新的數(shù)據(jù)和緩存規(guī)則,瀏覽器響應(yīng)數(shù)據(jù)后,再把規(guī)則寫入到緩存數(shù)據(jù)庫。

對(duì)比緩存在請(qǐng)求數(shù)上和沒有緩存是一致的,但如果是 304 的話,返回的僅僅是一個(gè)狀態(tài)碼而已,并沒有實(shí)際的文件內(nèi)容,因此 在響應(yīng)體體積上的節(jié)省是它的優(yōu)化點(diǎn)。它的優(yōu)化覆蓋了文章開頭提到過的請(qǐng)求數(shù)據(jù)的三個(gè)步驟中的最后一個(gè):“響應(yīng)”。通過減少響應(yīng)體體積,來縮短網(wǎng)絡(luò)傳輸時(shí)間。所以和強(qiáng)制緩存相比提升幅度較小,但總比沒有緩存好。

對(duì)比緩存是可以和強(qiáng)制緩存一起使用的,作為在強(qiáng)制緩存失效后的一種后備方案。實(shí)際項(xiàng)目中他們也的確經(jīng)常一同出現(xiàn)。

對(duì)比緩存有 2 組字段(不是兩個(gè)):

Last-Modified & If-Modified-Since

1、服務(wù)器通過 Last-Modified 字段告知客戶端,資源最后一次被修改的時(shí)間,例如

 
 
 
 
  1. Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT 

2、瀏覽器將這個(gè)值和內(nèi)容一起記錄在緩存數(shù)據(jù)庫中。

3、下一次請(qǐng)求相同資源時(shí)時(shí),瀏覽器從自己的緩存中找出“不確定是否過期的”緩存。因此在請(qǐng)求頭中將上次的 Last-Modified 的值寫入到請(qǐng)求頭的 If-Modified-Since 字段

4、服務(wù)器會(huì)將 If-Modified-Since 的值與 Last-Modified 字段進(jìn)行對(duì)比。如果相等,則表示未修改,響應(yīng) 304;反之,則表示修改了,響應(yīng) 200 狀態(tài)碼,并返回?cái)?shù)據(jù)。

但是他還是有一定缺陷的:

  • 如果資源更新的速度是秒以下單位,那么該緩存是不能被使用的,因?yàn)樗臅r(shí)間單位最低是秒。
  • 如果文件是通過服務(wù)器動(dòng)態(tài)生成的,那么該方法的更新時(shí)間永遠(yuǎn)是生成的時(shí)間,盡管文件可能沒有變化,所以起不到緩存的作用。

Etag & If-None-Match

為了解決上述問題,出現(xiàn)了一組新的字段 Etag 和 If-None-Match

Etag 存儲(chǔ)的是文件的特殊標(biāo)識(shí)(一般都是 hash 生成的),服務(wù)器存儲(chǔ)著文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新時(shí)間改變成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 變成了 If-None-Match。服務(wù)器同樣進(jìn)行比較,命中返回 304, 不命中返回新資源和 200。

Etag 的優(yōu)先級(jí)高于 Last-Modified

緩存小結(jié)

當(dāng)瀏覽器要請(qǐng)求資源時(shí)

  • 調(diào)用 Service Worker 的 fetch 事件響應(yīng)
  • 查看 memory cache
  • 查看 disk cache。這里又細(xì)分:
  • 如果有強(qiáng)制緩存且未失效,則使用強(qiáng)制緩存,不請(qǐng)求服務(wù)器。這時(shí)的狀態(tài)碼全部是 200
  • 如果有強(qiáng)制緩存但已失效,使用對(duì)比緩存,比較后確定 304 還是 200
  • 發(fā)送網(wǎng)絡(luò)請(qǐng)求,等待網(wǎng)絡(luò)響應(yīng)
  • 把響應(yīng)內(nèi)容存入 disk cache (如果 HTTP 頭信息配置可以存的話)
  • 把響應(yīng)內(nèi)容 的引用 存入 memory cache (無視 HTTP 頭信息的配置)
  • 把響應(yīng)內(nèi)容存入 Service Worker 的 Cache Storage (如果 Service Worker 的腳本調(diào)用了 cache.put())

一些案例

光看原理不免枯燥。我們編寫一些簡(jiǎn)單的網(wǎng)頁,通過案例來深刻理解上面的那些原理。

1、memory cache & disk cache

我們寫一個(gè)簡(jiǎn)單的 index.html,然后引用 3 種資源,分別是 index.js, index.css 和 mashroom.jpg。

我們給這三種資源都設(shè)置上 Cache-control: max-age=86400,表示強(qiáng)制緩存 24 小時(shí)。以下截圖全部使用 Chrome 的隱身模式。

首次請(qǐng)求

毫無意外的全部走網(wǎng)絡(luò)請(qǐng)求,因?yàn)槭裁淳彺娑歼€沒有。

再次請(qǐng)求 (F5)

第二次請(qǐng)求,三個(gè)請(qǐng)求都來自 memory cache。因?yàn)槲覀儧]有關(guān)閉 TAB,所以瀏覽器把緩存的應(yīng)用加到了 memory cache。(耗時(shí) 0ms,也就是 1ms 以內(nèi))

關(guān)閉 TAB,打開新 TAB 并再次請(qǐng)求

因?yàn)殛P(guān)閉了 TAB,memory cache 也隨之清空。但是 disk cache 是持久的,于是所有資源來自 disk cache。(大約耗時(shí) 3ms,因?yàn)槲募悬c(diǎn)小)

而且對(duì)比 2 和 3,很明顯看到 memory cache 還是比 disk cache 快得多的。

2、no-cache & no-store

我們?cè)?index.html 里面一些代碼,完成兩個(gè)目標(biāo):

  • 每種資源都(同步)請(qǐng)求兩次
  • 增加腳本異步請(qǐng)求圖片
 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

當(dāng)把服務(wù)器響應(yīng)設(shè)置為 Cache-Control: no-cache 時(shí),我們發(fā)現(xiàn)打開頁面之后,三種資源都只被請(qǐng)求 1 次。

 

這說明兩個(gè)問題:

  • 同步請(qǐng)求方面,瀏覽器會(huì)自動(dòng)把當(dāng)次 HTML 中的資源存入到緩存 (memory cache),這樣碰到相同 src 的圖片就會(huì)自動(dòng)讀取緩存(但不會(huì)在 Network 中顯示出來)
  • 異步請(qǐng)求方面,瀏覽器同樣是不發(fā)請(qǐng)求而直接讀取緩存返回。但同樣不會(huì)在 Network 中顯示。

總體來說,如上面原理所述,no-cache 從語義上表示下次請(qǐng)求不要直接使用緩存而需要比對(duì),并不對(duì)本次請(qǐng)求進(jìn)行限制。因此瀏覽器在處理當(dāng)前頁面時(shí),可以放心使用緩存。

當(dāng)把服務(wù)器響應(yīng)設(shè)置為 Cache-Control: no-store 時(shí),情況發(fā)生了變化,三種資源都被請(qǐng)求了 2 次。而圖片因?yàn)檫€多一次異步請(qǐng)求,總計(jì) 3 次。(紅框中的都是那一次異步請(qǐng)求)

 

 

這同樣說明:

  • 如之前原理所述,雖然 memory cache 是無視 HTTP 頭信息的,但是 no-store 是特別的。在這個(gè)設(shè)置下,memory cache 也不得不每次都請(qǐng)求資源。
  • 異步請(qǐng)求和同步遵循相同的規(guī)則,在 no-store 情況下,依然是每次都發(fā)送請(qǐng)求,不進(jìn)行任何緩存。

3、Service Worker & memory (disk) cache

我們嘗試把 Service Worker 也加入進(jìn)去。我們編寫一個(gè) serviceWorker.js,并編寫如下內(nèi)容:(主要是預(yù)緩存 3 個(gè)資源,并在實(shí)際請(qǐng)求時(shí)匹配緩存并返回)

 
 
 
 
  1. // serviceWorker.js 
  2. self.addEventListener('install', e => { 
  3.  // 當(dāng)確定要訪問某些資源時(shí),提前請(qǐng)求并添加到緩存中。 
  4.  // 這個(gè)模式叫做“預(yù)緩存” 
  5.  e.waitUntil( 
  6.  caches.open('service-worker-test-precache').then(cache => { 
  7.  return cache.addAll(['/static/index.js', '/static/index.css', '/static/mashroom.jpg']) 
  8.  }) 
  9.  ) 
  10. }) 
  11. self.addEventListener('fetch', e => { 
  12.  // 緩存中能找到就返回,找不到就網(wǎng)絡(luò)請(qǐng)求,之后再寫入緩存并返回。 
  13.  // 這個(gè)稱為 CacheFirst 的緩存策略。 
  14.  return e.respondWith( 
  15.  caches.open('service-worker-test-precache').then(cache => { 
  16.  return cache.match(e.request).then(matchedResponse => { 
  17.  return matchedResponse || fetch(e.request).then(fetchedResponse => { 
  18.  cache.put(e.request, fetchedResponse.clone()) 
  19.  return fetchedResponse 
  20.  }) 
  21.  }) 
  22.  }) 
  23.  ) 
  24. }) 

 

注冊(cè) SW 的代碼這里就不贅述了。此外我們還給服務(wù)器設(shè)置 Cache-Control: max-age=86400 來開啟 disk cache。我們的目的是看看兩者的優(yōu)先級(jí)。

當(dāng)我們首次訪問時(shí),會(huì)看到常規(guī)請(qǐng)求之外,瀏覽器(確切地說是 Service Worker)額外發(fā)出了 3 個(gè)請(qǐng)求。這來自預(yù)緩存的代碼。

第二次訪問(無論關(guān)閉 TAB 重新打開,還是直接按 F5 刷新)都能看到所有的請(qǐng)求標(biāo)記為 from SerciceWorker。

from ServiceWorker 只表示請(qǐng)求通過了 Service Worker,至于到底是命中了緩存,還是繼續(xù) fetch() 方法光看這一條記錄其實(shí)無從知曉。因此我們還得配合后續(xù)的

Network 記錄來看。因?yàn)橹鬀]有額外的請(qǐng)求了,因此判定是命中了緩存。

 

從服務(wù)器的日志也能很明顯地看到,3 個(gè)資源都沒有被重新請(qǐng)求,即命中了 Service Worker 內(nèi)部的緩存。

如果修改 serviceWorker.js 的 fetch 事件監(jiān)聽代碼,改為如下:

 
 
 
 
  1. // 這個(gè)也叫做 NetworkOnly 的緩存策略。 
  2. self.addEventListener('fetch', e => { 
  3. return e.respondWith(fetch(e.request)) 
  4. }) 

可以發(fā)現(xiàn)在后續(xù)訪問時(shí)的效果和修改前是 完全一致的。(即 Network 僅有標(biāo)記為 from ServiceWorker 的幾個(gè)請(qǐng)求,而服務(wù)器也不打印 3 個(gè)資源的訪問日志)

很明顯 Service Worker 這層并沒有去讀取自己的緩存,而是直接使用 fetch() 進(jìn)行請(qǐng)求。所以此時(shí)其實(shí)是 Cache-Control: max-age=86400 的設(shè)置起了作用,也就是 memory/disk cache。但具體是 memory 還是 disk 這個(gè)只有瀏覽器自己知道了,因?yàn)樗]有顯式的告訴我們。(個(gè)人猜測(cè)是 memory,因?yàn)椴徽搹暮臅r(shí) 0ms 還是從不關(guān)閉 TAB 來看,都更像是 memory cache)

瀏覽器的行為

所謂瀏覽器的行為,指的就是用戶在瀏覽器如何操作時(shí),會(huì)觸發(fā)怎樣的緩存策略。主要有 3 種:

  • 打開網(wǎng)頁,地址欄輸入地址: 查找 disk cache 中是否有匹配。如有則使用;如沒有則發(fā)送網(wǎng)絡(luò)請(qǐng)求。
  • 普通刷新 (F5):因?yàn)?TAB 并沒有關(guān)閉,因此 memory cache 是可用的,會(huì)被優(yōu)先使用(如果匹配的話)。其次才是 disk cache。
  • 強(qiáng)制刷新 (Ctrl + F5):瀏覽器不使用緩存,因此發(fā)送的請(qǐng)求頭部均帶有 Cache-control: no-cache(為了兼容,還帶了 Pragma: no-cache)。服務(wù)器直接返回 200 和最新內(nèi)容。

緩存的應(yīng)用模式

了解了緩存的原理,我們可能更加關(guān)心如何在實(shí)際項(xiàng)目中使用它們,才能更好的讓用戶縮短加載時(shí)間,節(jié)約流量等。這里有幾個(gè)常用的模式,供大家參考

模式 1:不常變化的資源

 
 
 
 
  1. Cache-Control: max-age=31536000 

通常在處理這類資源資源時(shí),給它們的 Cache-Control 配置一個(gè)很大的 max-age=31536000 (一年),這樣瀏覽器之后請(qǐng)求相同的 URL 會(huì)命中強(qiáng)制緩存。而為了解決更新的問題,就需要在文件名(或者路徑)中添加 hash, 版本號(hào)等動(dòng)態(tài)字符,之后更改動(dòng)態(tài)字符,達(dá)到更改引用 URL 的目的,從而讓之前的強(qiáng)制緩存失效 (其實(shí)并未立即失效,只是不再使用了而已)。

在線提供的類庫 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用這個(gè)模式。如果配置中還增加 public 的話,CDN 也可以緩存起來,效果拔群。

這個(gè)模式的一個(gè)變體是在引用 URL 后面添加參數(shù) (例如 ?v=xxx 或者 ?_=xxx),這樣就不必在文件名或者路徑中包含動(dòng)態(tài)參數(shù),滿足某些完美主義者的喜好。在項(xiàng)目每次構(gòu)建時(shí),更新額外的參數(shù) (例如設(shè)置為構(gòu)建時(shí)的當(dāng)前時(shí)間),則能保證每次構(gòu)建后總能讓瀏覽器請(qǐng)求最新的內(nèi)容。

特別注意: 在處理 Service Worker 時(shí),對(duì)待 sw-register.js(注冊(cè) Service Worker) 和 serviceWorker.js (Service Worker 本身) 需要格外的謹(jǐn)慎。如果這兩個(gè)文件也使用這種模式,你必須多多考慮日后可能的更新及對(duì)策。

模式 2:經(jīng)常變化的資源

 
 
 
 
  1. Cache-Control: no-cache 

這里的資源不單單指靜態(tài)資源,也可能是網(wǎng)頁資源,例如博客文章。這類資源的特點(diǎn)是:URL 不能變化,但內(nèi)容可以(且經(jīng)常)變化。我們可以設(shè)置 Cache-Control: no-cache 來迫使瀏覽器每次請(qǐng)求都必須找服務(wù)器驗(yàn)證資源是否有效。

既然提到了驗(yàn)證,就必須 ETag 或者 Last-Modified 出場(chǎng)。這些字段都會(huì)由專門處理靜態(tài)資源的常用類庫(例如 koa-static)自動(dòng)添加,無需開發(fā)者過多關(guān)心。

也正如上文中提到協(xié)商緩存那樣,這種模式下,節(jié)省的并不是請(qǐng)求數(shù),而是請(qǐng)求體的大小。所以它的優(yōu)化效果不如模式 1 來的顯著。

模式 3:非常危險(xiǎn)的模式 1 和 2 的結(jié)合 (反例)

 
 
 
 
  1. Cache-Control: max-age=600, must-revalidate 

不知道是否有開發(fā)者從模式 1 和 2 獲得一些啟發(fā):模式 2 中,設(shè)置了 no-cache,相當(dāng)于 max-age=0, must-revalidate。我的應(yīng)用時(shí)效性沒有那么強(qiáng),但又不想做過于長(zhǎng)久的強(qiáng)制緩存,我能不能配置例如 max-age=600, must-revalidate 這樣折中的設(shè)置呢?

表面上看這很美好:資源可以緩存 10 分鐘,10 分鐘內(nèi)讀取緩存,10 分鐘后和服務(wù)器進(jìn)行一次驗(yàn)證,集兩種模式之大成,但實(shí)際線上暗存風(fēng)險(xiǎn)。因?yàn)樯厦嫣徇^,瀏覽器的緩存有自動(dòng)清理機(jī)制,開發(fā)者并不能控制。

舉個(gè)例子:當(dāng)我們有 3 種資源: index.html, index.js, index.css。我們對(duì)這 3 者進(jìn)行上述配置之后,假設(shè)在某次訪問時(shí),index.js 已經(jīng)被緩存清理而不存在,但 index.html, index.css 仍然存在于緩存中。這時(shí)候?yàn)g覽器會(huì)向服務(wù)器請(qǐng)求新的 index.js,然后配上老的 index.html, index.css 展現(xiàn)給用戶。這其中的風(fēng)險(xiǎn)顯而易見:不同版本的資源組合在一起,報(bào)錯(cuò)是極有可能的結(jié)局。

除了自動(dòng)清理引發(fā)問題,不同資源的請(qǐng)求時(shí)間不同也能導(dǎo)致問題。例如 A 頁面請(qǐng)求的是 A.js 和 all.css,而 B 頁面是 B.js 和 all.css。如果我們以 A -> B 的順序訪問頁面,勢(shì)必導(dǎo)致 all.css 的緩存時(shí)間早于 B.js。那么以后訪問 B 頁面就同樣存在資源版本失配的隱患。

后記

這篇文章真心有點(diǎn)長(zhǎng),但已經(jīng)囊括了前端緩存的絕大部分,包括 HTTP 協(xié)議中的緩存,Service Worker,以及 Chrome 瀏覽器的一些優(yōu)化 (Memory Cache)。希望開發(fā)者們善用緩存,因?yàn)樗亲钊菀紫氲剑嵘沧畲蟮男阅軆?yōu)化策略。

參考文章

A Tale of Four Caches(但這篇文章把 Service Worker 的優(yōu)先級(jí)排在 memory cache 和 disk cache 之間,跟我實(shí)驗(yàn)效果并不相符。懷疑可能是 2 年來 chrome 策略的修改?)

Caching best practices & max-age gotchas


網(wǎng)站題目:一文讀懂前端緩存
轉(zhuǎn)載來于:http://www.dlmjj.cn/article/djooipc.html