目錄
- 一、前言
- 二、快取位置
- 1.Service Worker
- 2.Memory Cache
- 3.Disk Cache
- 4.Push Cache
- 三、快取程序分析
- 四、強快取
- 1.Expires
- 2.Cache-Control
- 3.Expires和Cache-Control兩者對比
- 五、協商快取
- 1.Last-Modified和If-Modified-Since
- 但是 Last-Modified 存在一些弊端:
- 2.ETag和If-None-Match
- 3.兩者之間對比:
- 1.Last-Modified和If-Modified-Since
- 六、快取機制
- 七、實際場景應用快取策略
- 1.頻繁變動的資源
- 2.不常變化的資源
- 八、用戶行為對瀏覽器快取的影響
一、前言
快取可以說是性能優化中簡單高效的一種優化方式了,一個優秀的快取策略可以縮短網頁請求資源的距離,減少延遲,并且由于快取檔案可以重復利用,還可以減少帶寬,降低網路負荷,
對于一個資料請求來說,可以分為發起網路請求、后端處理、瀏覽器回應三個步驟,瀏覽器快取可以幫助我們在第一和第三步驟中優化性能,比如說直接使用快取而不發起請求,或者發起了請求但后端存盤的資料和前端一致,那么就沒有必要再將資料回傳回來,這樣就減少了回應資料,
接下來的內容中我們將通過快取位置、快取策略以及實際場景應用快取策略來探討瀏覽器快取機制,

二、快取位置
從快取位置上來說分為四種,并且各自有優先級,當依次查找快取且都沒有命中的時候,才會去請求網路,
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
1.Service Worker
Service Worker 是運行在瀏覽器背后的獨立執行緒,一般可以用來實作快取功能,使用 Service Worker的話,傳輸協議必須為 HTTPS,因為 Service Worker 中涉及到請求攔截,所以必須使用 HTTPS 協議來保障安全,Service Worker 的快取與瀏覽器其他內建的快取機制不同,它可以讓我們自由控制快取哪些檔案、如何匹配快取、如何讀取快取,并且快取是持續性的,
Service Worker 實作快取功能一般分為三個步驟:首先需要先注冊 Service Worker,然后監聽到 install 事件以后就可以快取需要的檔案,那么在下次用戶訪問的時候就可以通過攔截請求的方式查詢是否存在快取,存在快取的話就可以直接讀取快取檔案,否則就去請求資料,
當 Service Worker 沒有命中快取的時候,我們需要去呼叫 fetch 函式獲取資料,也就是說,如果我們沒有在 Service Worker 命中快取的話,會根據快取查找優先級去查找資料,但是不管我們是從 Memory Cache 中還是從網路請求中獲取的資料,瀏覽器都會顯示我們是從 Service Worker 中獲取的內容,
2.Memory Cache
Memory Cache 也就是記憶體中的快取,主要包含的是當前中頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等,讀取記憶體中的資料肯定比磁盤快,記憶體快取雖然讀取高效,可是快取持續性很短,會隨著行程的釋放而釋放, 一旦我們關閉 Tab 頁面,記憶體中的快取也就被釋放了,
那么既然記憶體快取這么高效,我們是不是能讓資料都存放在記憶體中呢?
這是不可能的,計算機中的記憶體一定比硬碟容量小得多,作業系統需要精打細算記憶體的使用,所以能讓我們使用的記憶體必然不多,
當我們訪問過頁面以后,再次重繪頁面,可以發現很多資料都來自于記憶體快取

記憶體快取中有一塊重要的快取資源是preloader相關指令(例如<link rel="prefetch">)下載的資源,總所周知preloader的相關指令已經是頁面優化的常見手段之一,它可以一邊決議js/css檔案,一邊網路請求下一個資源,
需要注意的事情是,記憶體快取在快取資源時并不關心回傳資源的HTTP快取頭Cache-Control是什么值,同時資源的匹配也并非僅僅是對URL做匹配,還可能會對Content-Type,CORS等其他特征做校驗,
3.Disk Cache
Disk Cache 也就是存盤在硬碟中的快取,讀取速度慢點,但是什么都能存盤到磁盤中,比之 Memory Cache 勝在容量和存盤時效性上,
在所有瀏覽器快取中,Disk Cache 覆寫面基本是最大的,它會根據 HTTP Herder 中的欄位判斷哪些資源需要快取,哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求,并且即使在跨站點的情況下,相同地址的資源一旦被硬碟快取下來,就不會再次去請求資料,絕大部分的快取都來自 Disk Cache,關于 HTTP 的協議頭中的快取欄位,我們會在下文進行詳細介紹,
瀏覽器會把哪些檔案丟進記憶體中?哪些丟進硬碟中?
關于這點,網上說法不一,不過以下觀點比較靠得住:
- 對于大檔案來說,大概率是不存盤在記憶體中的,反之優先
- 當前系統記憶體使用率高的話,檔案優先存盤進硬碟
4.Push Cache
Push Cache(推送快取)是 HTTP/2 中的內容,當以上三種快取都沒有命中時,它才會被使用,它只在會話(Session)中存在,一旦會話結束就被釋放,并且快取時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也并非嚴格執行HTTP頭中的快取指令,
Push Cache 在國內能夠查到的資料很少,也是因為 HTTP/2 在國內不夠普及,這里推薦閱讀Jake Archibald的 HTTP/2 push is tougher than I thought 這篇文章,文章中的幾個結論:
- 所有的資源都能被推送,并且能夠被快取,但是 Edge 和 Safari 瀏覽器支持相對比較差
- 可以推送 no-cache 和 no-store 的資源
- 一旦連接被關閉,Push Cache 就被釋放
- 多個頁面可以使用同一個HTTP/2的連接,也就可以使用同一個Push Cache,這主要還是依賴瀏覽器的實作而定,出于對性能的考慮,有的瀏覽器會對相同域名但不同的tab標簽使用同一個HTTP連接,
- Push Cache 中的快取只能被使用一次
- 瀏覽器可以拒絕接受已經存在的資源推送
- 你可以給其他域名推送資源
如果以上四種快取都沒有命中的話,那么只能發起請求來獲取資源了,
那么為了性能上的考慮,大部分的介面都應該選擇好快取策略,通常瀏覽器快取策略分為兩種:強快取和協商快取,并且快取策略都是通過設定 HTTP Header 來實作的,
三、快取程序分析
瀏覽器與服務器通信的方式為應答模式,即是:瀏覽器發起HTTP請求 – 服務器回應該請求,那么瀏覽器怎么確定一個資源該不該快取,如何去快取呢?瀏覽器第一次向服務器發起該請求后拿到請求結果后,將請求結果和快取標識存入瀏覽器快取,瀏覽器對于快取的處理是根據第一次請求資源時回傳的回應頭來確定的,具體程序如下圖:

由上圖我們可以知道:
-
瀏覽器每次發起請求,都會先在瀏覽器快取中查找該請求的結果以及快取標識
-
瀏覽器每次拿到回傳的請求結果都會將該結果和快取標識存入瀏覽器快取中
以上兩點結論就是瀏覽器快取機制的關鍵,它確保了每個請求的快取存入與讀取,只要我們再理解瀏覽器快取的使用規則,那么所有的問題就迎刃而解了,本文也將圍繞著這點進行詳細分析,為了方便大家理解,這里我們根據是否需要向服務器重新發起HTTP請求將快取程序分為兩個部分,分別是強快取和協商快取,
四、強快取
強快取:不會向服務器發送請求,直接從快取中讀取資源,在chrome控制臺的Network選項中可以看到該請求回傳200的狀態碼,并且Size顯示from disk cache或from memory cache,強快取可以通過設定兩種 HTTP Header 實作:Expires 和 Cache-Control,
1.Expires
快取過期時間,用來指定資源到期的時間,是服務器端的具體的時間點,也就是說,Expires=max-age + 請求時間,需要和Last-modified結合使用,Expires是Web服務器回應訊息頭欄位,在回應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器快取取資料,而無需再次請求,
Expires 是 HTTP/1 的產物,受限于本地時間,如果修改了本地時間,可能會造成快取失效,Expires: Wed, 22 Oct 2018 08:41:00 GMT表示資源會在 Wed, 22 Oct 2018 08:41:00 GMT 后過期,需要再次請求,
2.Cache-Control
在HTTP/1.1中,Cache-Control是最重要的規則,主要用于控制網頁快取,比如當Cache-Control:max-age=300時,則代表在這個請求正確回傳時間(瀏覽器也會記錄下來)的5分鐘內再次加載資源,就會命中強快取,
Cache-Control 可以在請求頭或者回應頭中設定,并且可以組合使用多種指令:

public:所有內容都將被快取(客戶端和代理服務器都可快取),具體來說回應可被任何中間節點快取,如 Browser <-- proxy1 <-- proxy2 <-- Server,中間的proxy可以快取資源,比如下次再請求同一資源proxy1直接把自己快取的東西給 Browser 而不再向proxy2要,
private:所有內容只有客戶端可以快取,Cache-Control的默認取值,具體來說,表示中間節點不允許快取,對于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 會老老實實把Server 回傳的資料發送給proxy1,自己不快取任何資料,當下次Browser再次請求時proxy會做好請求轉發而不是自作主張給自己快取的資料,
no-cache:客戶端快取內容,是否使用快取則需要經過協商快取來驗證決定,表示不使用 Cache-Control的快取控制方式做前置驗證,而是使用 Etag 或者Last-Modified欄位來控制快取,需要注意的是,no-cache這個名字有一點誤導,設定了no-cache之后,并不是說瀏覽器就不再快取資料,只是瀏覽器在使用快取資料時,需要先確認一下資料是否還跟服務器保持一致,
no-store:所有內容都不會被快取,即不使用強制快取,也不使用協商快取
max-age:max-age=xxx (xxx is numeric)表示快取內容將在xxx秒后失效
s-maxage(單位為s):同max-age作用一樣,只在代理服務器中生效(比如CDN快取),比如當s-maxage=60時,在這60秒中,即使更新了CDN的內容,瀏覽器也不會進行請求,max-age用于普通快取,而s-maxage用于代理快取,s-maxage的優先級高于max-age,如果存在s-maxage,則會覆寫掉max-age和Expires header,
max-stale:能容忍的最大過期時間,max-stale指令標示了客戶端愿意接收一個已經過期了的回應,如果指定了max-stale的值,則最大容忍時間為對應的秒數,如果沒有指定,那么說明瀏覽器愿意接收任何age的回應(age表示回應由源站生成或確認的時間與當前時間的差值),
min-fresh:能夠容忍的最小新鮮度,min-fresh標示了客戶端不愿意接受新鮮度不多于當前的age加上min-fresh設定的時間之和的回應,

從圖中我們可以看到,我們可以將多個指令配合起來一起使用,達到多個目的,比如說我們希望資源能被快取下來,并且是客戶端和代理服務器都能快取,還能設定快取失效時間等等,
3.Expires和Cache-Control兩者對比
其實這兩者差別不大,區別就在于 Expires 是http1.0的產物,Cache-Control是http1.1的產物,兩者同時存在的話,Cache-Control優先級高于Expires;在某些不支持HTTP1.1的環境下,Expires就會發揮用處,所以Expires其實是過時的產物,現階段它的存在只是一種兼容性的寫法,
強快取判斷是否快取的依據來自于是否超出某個時間或者某個時間段,而不關心服務器端檔案是否已經更新,這可能會導致加載檔案不是服務器端最新的內容,那我們如何獲知服務器端內容是否已經發生了更新呢?此時我們需要用到協商快取策略,
五、協商快取
協商快取就是強制快取失效后,瀏覽器攜帶快取標識向服務器發起請求,由服務器根據快取標識決定是否使用快取的程序,主要有以下兩種情況:
-
協商快取生效,回傳304和Not Modified

-
協商快取失效,回傳200和請求結果

協商快取可以通過設定兩種 HTTP Header 實作:Last-Modified 和 ETag ,
1.Last-Modified和If-Modified-Since
瀏覽器在第一次訪問資源時,服務器回傳資源的同時,在response header中添加 Last-Modified的header,值是這個資源在服務器上的最后修改時間,瀏覽器接收后快取檔案和header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
瀏覽器下一次請求這個資源,瀏覽器檢測到有 Last-Modified這個header,于是添加If-Modified-Since這個header,值就是Last-Modified中的值;服務器再次收到這個資源請求,會根據 If-Modified-Since 中的值與服務器中這個資源的最后修改時間對比,如果沒有變化,回傳304和空的回應體,直接從快取讀取,如果If-Modified-Since的時間小于服務器中這個資源的最后修改時間,說明檔案有更新,于是回傳新的資源檔案和200

但是 Last-Modified 存在一些弊端:
- 如果本地打開快取檔案,即使沒有對檔案進行修改,但還是會造成 Last-Modified 被修改,服務端不能命中快取導致發送相同的資源
- 因為 Last-Modified 只能以秒計時,如果在不可感知的時間內修改完成檔案,那么服務端會認為資源還是命中了,不會回傳正確的資源
既然根據檔案修改時間來決定是否快取尚有不足,能否可以直接根據檔案內容是否修改來決定快取策略?所以在 HTTP / 1.1 出現了 ETag 和If-None-Match
2.ETag和If-None-Match
Etag是服務器回應請求時,回傳當前資源檔案的一個唯一標識(由服務器生成),只要資源有變化,Etag就會重新生成,瀏覽器在下一次加載資源向服務器發送請求時,會將上一次回傳的Etag值放到request header里的If-None-Match里,服務器只需要比較客戶端傳來的If-None-Match跟自己服務器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了,如果服務器發現ETag匹配不上,那么直接以常規GET 200回包形式將新的資源(當然也包括了新的ETag)發給客戶端;如果ETag是一致的,則直接回傳304知會客戶端直接使用本地快取即可,

3.兩者之間對比:
- 首先在精確度上,Etag要優于Last-Modified,
Last-Modified的時間單位是秒,如果某個檔案在1秒內改變了多次,那么他們的Last-Modified其實并沒有體現出來修改,但是Etag每次都會改變確保了精度;如果是負載均衡的服務器,各個服務器生成的Last-Modified也有可能不一致,
- 第二在性能上,Etag要遜于Last-Modified,畢竟Last-Modified只需要記錄時間,而Etag需要服務器通過演算法來計算出一個hash值,
- 第三在優先級上,服務器校驗優先考慮Etag
六、快取機制
強制快取優先于協商快取進行,若強制快取(Expires和Cache-Control)生效則直接使用快取,若不生效則進行協商快取(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商快取由服務器決定是否使用快取,若協商快取失效,那么代表該請求的快取失效,回傳200,重新回傳資源和快取標識,再存入瀏覽器快取中;生效則回傳304,繼續使用快取,具體流程圖如下:

看到這里,不知道你是否存在這樣一個疑問:如果什么快取策略都沒設定,那么瀏覽器會怎么處理?
對于這種情況,瀏覽器會采用一個啟發式的演算法,通常會取回應頭中的 Date 減去 Last-Modified 值的 10% 作為快取時間,
七、實際場景應用快取策略
1.頻繁變動的資源
Cache-Control: no-cache
對于頻繁變動的資源,首先需要使用Cache-Control: no-cache 使瀏覽器每次都請求服務器,然后配合 ETag 或者 Last-Modified 來驗證資源是否有效,這樣的做法雖然不能節省請求數量,但是能顯著減少回應資料大小,
2.不常變化的資源
Cache-Control: max-age=31536000
通常在處理這類資源時,給它們的 Cache-Control 配置一個很大的 max-age=31536000 (一年),這樣瀏覽器之后請求相同的 URL 會命中強制快取,而為了解決更新的問題,就需要在檔案名(或者路徑)中添加 hash, 版本號等動態字符,之后更改動態字符,從而達到更改參考 URL 的目的,讓之前的強制快取失效 (其實并未立即失效,只是不再使用了而已),
在線提供的類別庫 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用這個模式,
八、用戶行為對瀏覽器快取的影響
所謂用戶行為對瀏覽器快取的影響,指的就是用戶在瀏覽器如何操作時,會觸發怎樣的快取策略,主要有 3 種:
- 打開網頁,地址欄輸入地址: 查找 disk cache 中是否有匹配,如有則使用;如沒有則發送網路請求,
- 普通重繪 (F5):因為 TAB 并沒有關閉,因此 memory cache 是可用的,會被優先使用(如果匹配的話),其次才是 disk cache,
- 強制重繪 (Ctrl + F5):瀏覽器不使用快取,因此發送的請求頭部均帶有
Cache-control: no-cache(為了兼容,還帶了Pragma: no-cache),服務器直接回傳 200 和最新內容,
作者:浪里行舟
鏈接:深入理解瀏覽器的快取機制
來源:github
著作權歸作者所有,商業轉載請聯系作者獲得授權,非商業轉載請注明出處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/251587.html
標籤:其他
