前端性能優化 之 瀏覽器緩存

做爲開發人員你們都知道,從網絡上獲取資源成本比較高,客戶端須要和服務端要進行屢次通信,若是能有效利用緩存,能夠極大提升 web 應用的性能,因此有必要詳細瞭解一個關於緩存的各個細節。javascript

爲防止出現理出現解上的誤差,在開始以前咱們約定關於緩存處理指的是:當用戶打個某個網址或者應用 之後 把它關閉,而後 再次打開 的的狀況。html

若是用戶主動點擊了 刷新 或者 強制刷新(CTRL+F5) 的狀況在最後面再詳細說。前端

概念篇

瀏覽器緩存分兩個類型:非驗證性緩存驗證性緩存java

非驗證性緩存:瀏覽器根據過時時間來判斷是否使用緩存,若是在有效期內,直接從瀏覽器緩存中讀取文件,不發生http請求,涉及到的 header 字段有 Cache-Controlexpirespragmanginx

驗證性緩存:給服務端發送請求時,在 header 裏附帶條件,服務端在處理請求時根據指定條件作出判斷,若是符合條件則返回一個 304 狀態碼並返回空的 body ,瀏覽器在接收到 304 狀態碼後得知本地緩存依然有效,直接從本地緩存讀取;若是條件爲假則返回 200 狀態碼並返回指定資源。涉及到的 header 字段有 etaglast-modifiedweb

從以上內容可知,非驗證性緩存最優,他從本地讀取,甚至都不會發生網絡請求;其次是驗證性緩存,他會產生網絡請求,但若是緩存可用,它返回的 body 爲空,數據傳輸量也是很是小。api

瀏覽器在判斷緩存時的順序是:瀏覽器

非驗證性緩存 > 驗證性緩存緩存

下面開始逐個來說,會涉及到一些服務器方面的知識,若是對 nginx 不熟,推薦看一下這篇文章:前端工程師學習 Nginx 入門篇bash

非驗證性緩存

非驗證性緩存 主要以 Cache-Controlexpirespragma 這三個消息頭控制。因爲 pragma 是 HTTP/1.0 中的規範,它在響應中的行爲沒有確切規範,並且他能夠被 Cache-Control 覆蓋,因此這裏咱們不說了,只看 Cache-Controlexpires

Cache-Control

若是在 nginx 的配置文件裏有以下配置:

# nginx.conf
add_header Cache-Control max-age=20;
複製代碼

這個指令的含義是指定資源的過時時間是 20s ,在 20s 內,若是瀏覽器對這個資源有重複請求,將不會產生 http 請求,直接從瀏覽器緩存中讀取。請求響應頭以下:

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 14:10:36 GMT
Content-Type: text/html
Content-Length: 755
Last-Modified: Thu, 27 Sep 2018 22:44:02 GMT
Connection: keep-alive
Cache-Control: max-age=20
Accept-Ranges: bytes
複製代碼

其餘參數先不看,能夠看到的是 max-age=20 定義了過時時間是 20s,同時能夠在瀏覽器和服務端 log 中驗證,確實沒有發生 http 請求。

Cache-control 用的最多的是 max-age ,但它還有其餘不少指令,分別表明不一樣的含義:

Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>
複製代碼

感興趣的朋友能夠在 MDN Cache-control 上詳細瞭解。

expires

如今從新配置 nginx 以下:

# nginx.conf
#add_header Cache-Control max-age=20;
add_header expires 'Thu, 27 Sep 2019 22:44:02 GMT';
複製代碼

註釋掉 Cache-Control,添加一個 expires 消息頭,值是將來的某一時刻,這時再訪問頁面,若是緩存命中,響應頭裏會有以下信息:

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 14:45:15 GMT
Content-Type: text/html
Content-Length: 755
Last-Modified: Thu, 27 Sep 2018 22:44:02 GMT
Connection: keep-alive
expires: Thu, 27 Sep 2019 22:44:02 GMT
Accept-Ranges: bytes
複製代碼

從響應信息頭中能夠明確看出過時時間,在這個截至時間內訪問都不會產生新的 http 請求,直接從瀏覽器緩存中讀取資源。

Cache-Control 優先級高於 expires

這個時候若是咱們從新編輯 nginx 配置文件以下:

# nginx.conf
add_header Cache-Control max-age=20;
add_header expires 'Thu, 27 Sep 2019 22:44:02 GMT';
複製代碼

再訪問如能夠看到以下響應頭信息:

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 14:49:50 GMT
Content-Type: text/html
Content-Length: 755
Last-Modified: Thu, 27 Sep 2018 22:44:02 GMT
Connection: keep-alive
Cache-Control: max-age=20
expires: Thu, 27 Sep 2019 22:44:02 GMT
Accept-Ranges: bytes
複製代碼

因爲 Cache-Control 優先級高於 expires ,在實際測試過程當中可知緩存在 20s 後就過時了。

expires 是http 1.0中定義的消息頭,Cache-Control 是 http 1.1 中定義的消息頭,若是他們同時存在 Cache-Control 會覆蓋 expires,並且 expires 返回的時間是服務器時間,若是服務器時間與客戶端時間不一致,會形成很大偏差,而且 http 1.1 已經被向乎全部瀏覽器支持,因此使用 Cache-Control 就好。

驗證性緩存

看完了非驗證性緩存瞭解到他對靜態資源很是重要,能夠極大節省帶寬,提高 web 應用性能。但有些資源有必定的時效性,須要常常去服務器驗證是否有更新,如 html 、 api 接口等,這個時候就須要用到驗證性緩存。

如前所述,驗證性更新須要向服務端發送一個請求,若是服務端判斷沒有更新,返回一個 304 狀態碼並返回一個空的 body 信息體,瀏覽器能夠直接從本地緩存讀取資源。若是有更新,返回 200 狀態碼並將最新資源一併返回。

驗證性緩存主要由 last-modifiedetag 這兩個消息頭控制,接下來咱們依次來看。

last-modified

last-modified 是 nginx 默認開啓的,因此不用手動去配置它。

因爲 非驗證性緩存 的優先級要高於 驗證性緩存,因此測試的時候須要將他們設爲無效,要否則看不到效果:

# nginx.conf
add_header Cache-Control max-age=0;
#add_header expires 'Thu, 27 Sep 2019 22:44:02 GMT'; # Cache-Control優先級較高,設置一個就好
複製代碼

如今再看,若是再次訪問,請求頭會帶上相似以下字段:

...
If-Modified-Since: Thu, 27 Sep 2018 22:37:45 GMT
...
複製代碼

這個時候再測試,對於 資源未更新 的狀況,響應頭以下:

HTTP/1.1 304 Not Modified
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 15:29:06 GMT
Last-Modified: Thu, 27 Sep 2018 22:37:45 GMT # 明確標示最後修改時間
Connection: keep-alive
Cache-Control: max-age=0
複製代碼

也能看到瀏覽器端是直接從緩存中取的內容。

對於 資源發生過更新 的狀況,響應頭以下:

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 15:29:06 GMT
Content-Type: application/javascript
Content-Length: 4770
Last-Modified: Fri, 28 Sep 2018 15:29:03 GMT # 明確標示最後修改時間
Connection: keep-alive
Cache-Control: max-age=0
Accept-Ranges: bytes
複製代碼

見下圖:

status

last-modifiedif-modified-since 是成對出現的,分別的做用是:

  • last-modified 在響應頭裏,服務器告訴瀏覽器,這個資源的最後修改時間是什麼
  • if-modified-since 在請求頭裏,告訴服務器我所請求的這個資源最後修改時間是什麼。服務器根據這個值來判斷,若是這個值和服務端這個資源現有的值一致,直接返回 304 和空的 body,若是和服務端現有的值不一致(資源已經更新),則返回 200 和最新資源。

根據這兩個時間,服務器和瀏覽器就可以決定資源是不是最新的,是否可使用本地緩存。

etag

ETag HTTP 響應頭是資源的特定版本的標識符,它和 last-modified 相似,都是爲了實現資源的驗證性緩存,但 etag 精度更高( last-modified 只能精確到秒),同時 etag 還能避免「空中碰撞」,詳細的解釋能夠看 MDN 的 Etag 介紹。

下面直接來看他的實現:

# nginx.conf
etag on; # 手動開啓 etag
add_header Cache-Control max-age=0;
#add_header expires 'Thu, 27 Sep 2019 22:44:02 GMT'; 
add_header Last-Modified ''; # 爲了測試 etag 的效果,將 last-modified 設爲無效
複製代碼

如今再看,若是再次訪問,請求頭會帶上以下字段:

...
If-None-Match: "5bad5bb9-13a3f"
...
複製代碼

這個時候再測試,對於 資源未更新 的狀況,響應頭以下:

HTTP/1.1 304 Not Modified
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 22:43:11 GMT
Connection: keep-alive
ETag: "5bad5bb9-13a3f"
Cache-Control: max-age=0
複製代碼

能夠看到瀏覽器端是直接從緩存中取的內容。

對於 資源發生過更新 的狀況,響應頭以下:

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 28 Sep 2018 22:43:11 GMT
Content-Type: application/javascript
Content-Length: 4770
Connection: keep-alive
ETag: "5baeae7a-12a2"
Cache-Control: max-age=0
Accept-Ranges: bytes
複製代碼

能夠看到服務器將最新的內容傳輸給瀏覽器,並返回 200 code

etagif-none-match 是成對出現的,

  • etag 是服務器根據必定規則生成的資源‘指紋’,傳遞給客戶端,客戶端將其與緩存一塊兒保存
  • if-none-match 是客戶端在向服務端請求指定資源時,將本地的 etag 值經過信息頭傳遞給服務端,服務端與其當前版本的資源的ETag進行比較,若是兩個值匹配(即資源未更改),服務器將返回不帶任何內容的304未修改狀態,告訴客戶端緩存版本可用。若是 etag 值匹配不成功,返回 200 code 和資源內容。

用戶主動刷新行爲

當用戶主動點擊了 刷新 或者 強刷刷新,瀏覽器會在請求頭信息裏附上不一樣的字段,來告訴服務器如何處理這個行爲。

用戶點擊 刷新

當用戶點擊刷新時,瀏覽器在請求頭裏會加上以下字段:

If-Modified-Since: Fri, 28 Sep 2018 22:43:06 GMT # 若是開啓了 If-Modified
If-None-Match: "5baeae7a-12a2" # 若是開啓了 etag
Cache-Control: max-age=0
複製代碼

這時即使 Cache-Control 設置了更大的值,也不會從本地緩存中直接讀取,而是要發送一條新的請求去服務器驗證資源是否有更新,因此這個時間就跳過了第一階段的 非驗證性緩存,進入 驗證性緩存。

用戶點擊 強制刷新

當用戶點擊強制刷新時,瀏覽器在請求頭裏會加上以下字段:

Pragma: no-cache
Cache-Control: no-cache
複製代碼

能夠看到,即使 Cache-Control 設置了更大的值,也不會從緩存中直接讀取,並且不會發送 If-Modified-SinceIf-None-Match ,也就是說服務器得不到資源的最後更新時間和 etag 值,不管如何都會返回最新的資源。

因此當用戶 強制刷新 時,瀏覽器主動跳過了 非驗證性緩存 和 驗證性緩存,直接從服務端獲取最新資源。

這也是爲何需求方找咱們看問題的時候,咱們老是喜歡讓他們強制刷新的緣由...

參考文檔

相關文章
相關標籤/搜索