淺談瀏覽器快取機制

Harry楊醬發表於2018-11-09

前文

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

快取過程分析

快取過程分析

由此可知:

  • 瀏覽器每次傳送請求前,都會從瀏覽器快取中查詢快取及其快取標識
  • 瀏覽器每次請求到資料後,都會將資料及其快取標識存入瀏覽器快取

快取規則

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檔案的處理方式不同;

service worker

Service workers 本質上充當Web應用程式與瀏覽器之間的代理伺服器,也可以在網路可用時作為瀏覽器和網路間的代理。它們旨在(除其他之外)使得能夠建立有效的離線體驗,攔截網路請求並基於網路是否可用以及更新的資源是否駐留在伺服器上來採取適當的動作。他們還允許訪問推送通知和後臺同步API。

service worker的特點:

  • 要求 HTTPS 環境,開發過程中,一般瀏覽器也允許 host 為 localhost 或者 127.0.0.1
  • 執行在它自己的全域性指令碼上下文中
  • 不繫結到具體的網頁,無法修改網頁中的元素,因為它無法訪問 DOM
  • 一旦被 install,就永遠存在,除非被手動 unregister
  • 非同步實現,內部大都是通過 Promise 實現,依賴 HTML5 fetch API
  • Service Worker 的快取機制是依賴 Cache API 實現的

具體請移步

Cookie

  • Cookie大小在4k左右
  • http請求會預設帶上
  • 域名、埠相同即可共享(CSRF)
  • 一般不直接使用,會進行簡單的封裝,請移步
  • Cookie的常見屬性
key value
expires Cookie失效時間(絕對時間)若不設定,瀏覽器關閉即刪除
Max-Age Cookie失效時間,相對時間,優先順序高於expires
path 設定哪些路徑帶上cookie,一般預設為'/'
Domain 設定哪些域名帶上cookie,一般為當前一級域名
Secure 只在https下才能傳送cookie
HttpOnly js指令碼獲取不到,且只能在http上才能傳送cookie(防止xss)
SameSite 是否可能作為第三方 cookie(防止CSRF)

localStorage、sessionStorage

相同點

  • 儲存大小都在5M左右
  • 會返回一個bol來標識是否儲存成功
  • 同源策略限制
  • 不能直接和伺服器通訊,可以通過指令碼注入請求引數裡面

不同點

  • localStorage儲存資料永久性直到人為刪除、sessionStorage和視窗的生命週期相同
  • localStorage滿足同源策略可以跨視窗儲存,sessionStorage不行

最後

推薦一下自己的個人公眾號:前端精讀(每日定時推送一篇前端好文)

前端每日精讀

相關文章