淺談瀏覽器快取機制
在前端開發中,效能一直都是被大家所重視的一點,然而判斷一個網站的效能最直觀的就是看網頁開啟的速度。其中提高網頁反應速度的一個方式就是使用快取。一個優秀的快取策略可以縮短網頁請求資源的距離,減少延遲,並且由於快取檔案可以重複利用,還可以減少頻寬,降低網路負荷。
快取過程分析

由此可知:
- 瀏覽器每次傳送請求前,都會從瀏覽器快取中查詢快取及其快取標識
- 瀏覽器每次請求到資料後,都會將資料及其快取標識存入瀏覽器快取
快取規則
1、強制快取階段:先在本地查詢該資源,如果有發現該資源,而且該資源還沒有過期,就使用這一個資源,完全不會發送http請求到伺服器
2、協商快取階段:如果在本地快取找到對應的資源,但是不知道該資源是否過期或者已經過期,則發一個http請求到伺服器,然後伺服器判斷這個請求,如果請求的資源在伺服器上沒有改動過,則返回304,讓瀏覽器使用本地找到的那個資源;
3、啟發式快取階段
4、快取失敗階段:當伺服器發現請求的資源已經修改過,或者這是一個新的請求(在本來沒有找到資源),伺服器則返回該資源的資料,並且返回200, 當然這個是指找到資源的情況下,如果伺服器上沒有這個資源,則返回404。
快取標識
1、強制快取階段
由上面可以知道強制快取是直接使用本地的快取,不傳送http請求,那麼判斷它是否要進行強制快取的標誌是什麼呢?
- Expires http1.0
Expires是HTTP/1.0控制網頁快取的欄位,其值為伺服器返回該請求結果快取的到期時間(絕對時間),即再次發起該請求時,如果客戶端的當前時間小於Expires的值時,直接使用快取結果。
缺點:因為是絕對時間,如果客戶端與服務端的時間因為某些原因(例如時區不同;客戶端和服務端有一方的時間不準確)發生誤差,強快取可能直接失效。
app.get('/1.jpg',function(req,res,next){ ... res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toUTCString()) }) 複製程式碼
- Cache-Control http1.1
在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁快取,它有幾個選項: 1、public : 所有的內容都被快取(客戶端和代理伺服器都可快取) 2、private : 所有的內容都被快取(客戶端快取,cache-control的預設值) 3、no-cache: 客戶端快取,但是是否使用快取得通過協商快取來決定 4、no-store: 所有內容都不快取,既不強制快取,又不協商快取 5、max-age=num(num的單位是秒) : 快取內容在num秒後失效,num為相對時間
app.get('/2.jpg',function(req,res,next){ res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toUTCString()) res.setHeader('Cache-Control', 'max-age=20') ... }) 複製程式碼
經過強快取後,客戶端再請求,如果快取標誌有效,則返回灰色200,資料從瀏覽器快取中取出。 優先順序:Cache-Control > expires
2、啟發式快取階段
當快取過期時間的欄位一個都沒有的時候,瀏覽器下次並不會直接進入協商階段,而是先進入啟發式快取階段,你可以通過關閉伺服器,重新整理頁面來觀察。 它根據響應頭中2個時間欄位 Date 和 Last-Modified 之間的時間差值,取其值的10%作為快取時間週期。 也就是說,當存有 Last-Modified欄位的時候,即使是斷網,且強快取都失效後,也有一定時間是直接讀取快取檔案的。 etag是沒有這個階段的。
3、協商快取階段
協商階段就是當強快取階段失效的時候,http請求會攜帶快取標誌符向伺服器發起請求,由伺服器來決定是否使用快取,瀏覽器根據返回到code碼來決定是否從瀏覽器快取中拿去資料。
- Last-Modified / If-Modified-Since http1.0
Last-Modified是伺服器響應請求時,返回該資原始檔在伺服器最後被修改的時間。 If-Modified-Since是再次請求該資原始檔的時候,會帶上上次請求中的Last-Modified時間,伺服器通過對比Last-Modified / If-Modified-Since,返回200,則有更新,從伺服器拉取資料,304則使用快取檔案
app.get('/3.jpg',function(req,res,next){ ... let ifModifiedSince = req.headers['if-modified-since'] let LastModified = stat.ctime.toGMTString() if(!!ifModifiedSince && LastModified === ifModifiedSince){ res.statusCode = 304 res.end() } if(!ifModifiedSince|| (!!ifModifiedSince && LastModified !== ifModifiedSince)){ res.setHeader('Last-Modified', LastModified) } ... }) }) 複製程式碼
- Etag / If-None-Match http1.1
Etag是伺服器響應請求時,返回當前資原始檔的一個唯一標識(由伺服器根據檔案資訊演算法生成,類似hash) If-None-Match是客戶端再次發起該請求時,攜帶上次請求返回的唯一標識Etag值,通過此欄位值告訴伺服器該資源上次請求返回的唯一標識值。伺服器收到該請求後,發現該請求頭中含有If-None-Match,則會根據If-None-Match的欄位值與該資源在伺服器的Etag值做對比,一致則返回304,代表資源無更新,繼續使用快取檔案;不一致則重新返回資原始檔,狀態碼為200
app.get('/4.jpg',function(req,res,next){ reponseHandle(req,res,(stat)=>{ let ifNoneMatch = req.headers['if-none-match'] let etag = ..... if(!ifNoneMatch || (!!ifNoneMatch && ifNoneMatch!== etag)){ res.setHeader('ETag', etag) } if(!!ifNoneMatch && ifNoneMatch === etag){ res.statusCode = 304 res.end() } }) }) 複製程式碼
Etag / If-None-Match優先順序高於Last-Modified / If-Modified-Since,同時存在則只有Etag / If-None-Match生效
由此可見,強制快取優先於協商快取進行,若強制快取(Expires和Cache-Control)生效則直接使用快取,若不生效則進行協商快取(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商快取由伺服器決定是否使用快取,若協商快取失效,那麼代表該請求的快取失效,重新獲取請求結果,再存入瀏覽器快取中;生效則返回304,繼續使用快取
4、一些常見且需要知道的識別符號
欄位 | 說明 |
---|---|
pragma | http1.0,值為no-cache為禁用快取 |
vary | 基於欄位區分快取版本(res header) |
Date | 傳送響應報文的時間(啟發式快取、代理伺服器快取) |
Age | 檔案存於伺服器的時間 |
accept-encoding | 請求伺服器返回的檔案型別 |
referer | 傳送請求的源域名 |
伺服器快取
CDN快取
CDN快取,也叫閘道器快取、反向代理快取。瀏覽器先向CDN閘道器發起WEB請求,閘道器伺服器後面對應著一臺或多臺負載均衡源伺服器,會根據它們的負載請求,動態地請求轉發到合適的源伺服器上。
CDN的優勢:
- CDN節點解決了跨運營商和跨地域訪問的問題,訪問延時大大降低
- 大部分請求在CDN邊緣節點完成,CDN起到了分流作用,減輕了源站的負載。
- 通過http響應頭中的Cache-control: max-age的欄位來設定CDN邊緣節點資料快取時間
HTML5快取
app cache
因為幾個歷史原因,app cache已經不推薦使用,從web標準移除了。原因如下:
- 使用了manifest後,沒辦法清空這些快取,只能更新快取,或者得使用者自己去清空瀏覽器的快取;
- 假如更新的資源中有一個資源更新失敗了,那麼所有的資源就會全部更新失敗,將用回上一版本的快取;
- 主頁會被強制快取(使用了manifest的頁面),並且無法清除;
- appache檔案可能會無法被及時更新,因為各大瀏覽器對於appcache檔案的處理方式不同;