[Front-end] Browser Cache : 熟悉的陌生人 I

Huge Gun
12 min readJul 7, 2020

--

Cache 是效能優化很重要的一項,也是在開發時容易忽略的點,常常就是 Ctrl + F5 先全部刷新再說,但這樣做其實我們對被刷新的 Cache 是陌生的,這系列就來說說 Cache 怎麼回事吧,如有不對也可以跟我說!

第一篇用三個點來開啟進入 Browser Cache 的第一站:

  1. Cache 是什麼
  2. Cache 的機制
  3. HTTP Header Cache 介紹

Cache 是什麼

我們在 Browser 上進入一個網站時看到的畫面,是由很多的 Image、Dom、Videos 等等組合來的,我們在上面的操作也都依賴執行要用的 JS、CSS 等檔案,而剛剛提到的全部 Files 初始都存在 Server 上,而不再我們自己的 Client 所以會去跟 Server 要,執行的流程大致上如下圖:

Browser source load

但如果每次都全部重拿是很浪費效能的,且時間比較久,所以 Cache 在這時候就派上用場了,簡單來說就是為了減省 Client 去跟 Server 要 Source 的流量 & 提高 Render Page 的效能所採用的方法,以上圖來說就是優化甚至略過 2~4 的步驟,特別是 Static Source (html、js、css、image 等...),通常我們對 Static Source 效能在意的 2 個點,如下:

  1. Load Static Source 的速度
  2. Page Render 的速度

試想看看,如果每次打開同一個網站,比方說 YouTube 好了,每張看過的圖都全部重新下載一次,效能跟消耗的流量都是過多的浪費, Render 速度一定比用 Cache 來的慢很多,萬一又遇上網速本身就慢的 User,幾乎就是一場悲劇。

Cache 的機制

既然知道了 Cache 存在的目的,接著來看一下對於 Browser 來說 Cache 機制的分類吧! 常見的一共可以分成兩大類

  1. HTTP Cache
  2. Data Store

HTTP Cache 可以先假想成是當每次 Client 去跟 Server 要 Source 時,都會先執行的檢查,有點類似 Get Source 的警衛,只要警衛檢查這次要 Render 的 Source 與 Client Cache 沒有差異,就不用再去發 HTTP Request,採用 Client Cache Render 即可,詳細的後面會說。

Data Store 大家就比較熟悉了,它儲存的 Data 可能是已經從 Server 拿到的 Info 了,一般都是在多個頁面中常要使用到的,例如: Cookie、Session 裡面放的那些 Token、Page 語系之類的。

這篇主要會放在 HTTP Cache 的部分,至於 Data Store 會再本系列的另外一篇來說,接著我們就進入本篇重點吧!!

HTTP Cache 介紹

前面提到 HTTP Cache 類似 Get Source 的警衛,而警衛呢其實有兩個分別是 “強 Cache” & “協商 Cache”,它們之間是相輔相成的,那我們先來看看當一個 Get Source 發生時 Client 跟 Server 的溝通,如下圖:

Cache flow

圖中可以看到進入(3) 的時候,最先執行的是 Case 1 之後 才是 Case 2 & 3,這個順序也是 Cache 的執行順序,固定都是 “強 Cache 先協商 Cache 後” 這點務必牢記,接著我們一點點來細看這兩種機制吧!!

強 Cache :

它的特性是 如果該項 Source 有 Match 到強 Cache,就不發出 HTTP Request 會直接用 Client Cache,若沒有 Match 到就會進到協商 Cache 階段,而判斷的依據是通過 Expires Cache-Control 兩個 Respond Header Tag 來實現檢查 Source 有無過期,如下:

1. Expires :

它是早期 HTTP 1.0 時提出的方案,用來表示 Source 過期時間的 String,可以在 Respond Header 中找到。

長相 :

Expires: Mon, 15 Jul 2019 16:22:58 GMT

範例表示該項 Source 在 2019年7月15日 4點 22分 58秒之前的 Source 來源都是可以用 Client Cache 的。

如果我們打開 Chrome DevTools 來看的話,就如下圖

Source

特性 :

  • Browser 在第一次跟 Server 拿某個 Source 的時候, Server 會紀錄時間,之後在 Respond Header 中加上這個 Tag 回給 Browser (像上圖那樣)。
  • Browser 收到之後,會把 Source & 這次所有 Respond Header 一起存起來。
  • 之後當強 Cache 有 Match 到時,那次 Request 的 Respond Header 並不是來自 Server,而是來自之前的 Cache 存起來的 Respond Header。
  • 第二次之後當 Browser 又要拿這項 Source 時,就會直接先找 Client Cache,找到的話就比較當下時間跟 Cache 中的 Expires Tag String
  • 若是比較後時間沒過期,就直接用 Cache 的即可
  • 若已經過期的話,就要向 Server 重新發送一次 HTTP Request 拿新的,會再執行一次剛剛說到的第一點 & 第二點。

缺點 :

  • Expires Tag 的 String 是絕對時間,但 Client 與 Server 兩端的時間不一定是 Sync 的,如果 User 在 Client 上修改了自己 Local 的時間,那兩端對不起來,就會造成 Cache 功能失效。

也因為 Expires 兩端時間 Sync 容易對不上的這項缺點,所以在 HTTP 1.1 出現了 Cache-Control,我們接著來看看吧!!

2. Cache-Control :

為了改上前面 Expires 問題而出現的新 Tag,它改用了相對時間來判斷 Cache 是否過期,你也可以在 Respond Header 中找到它。

長相 :

Cache-Control:max-age=31536000

範例表示當 Clint 收到上面這個 Respond ,代表在往後的 31536000 秒 (1年) 之內,該項 Source 都可以拿取 Client Cache 來做使用。

如果我們打開 Chrome DevTools 來看的話,就如下圖:

Source

特性 :

  • Browser 在第一次跟 Server 拿某個 Source 的時候, Server 會紀錄時間,之後在 Respond Header 中加上這個 Tag 回給 Browser (像上圖那樣)。
  • Browser 收到之後,會把 Source & 這次所有 Respond Header 一起存起來。
  • 之後當強 Cache 有 Match 到時,那次 Request 的 Respond Header 並不是來自 Server,而是來自之前的 Cache 存起來的 Respond Header。
  • 第二次之後當 Browser 又要拿這項 Source 時,就會直接先找 Client Cache,找到的話會根據第一次 Request 時間 & Cache-Control 設定的有效期,計算這個 Source 的過期時間,再拿這個時間跟當前的請求時間比較。
  • 若是比較後時間沒過期,就直接用 Cache 的即可
  • 若已經過期的話,就要向 Server 重新發送一次 HTTP Request 拿新的,會再執行一次剛剛說到的第一點 & 第二點。

目前看到這邊會發現 Expires & Cache-Control 只有在計算時間上的部分有差異,接著我們再補充一些對於兩者的小知識點:

Additional Tips:

  • Expires & Cache-Control 這兩個 Cache 警衛 可以只啟用一個,也可以同時啟用
  • 當兩個 同時啟用時,Cache-Control 優先級高於 Expires,Browser 會先用 Cache-Control 去檢查比對時間。

至於好奇詳細為什麼會這樣設定的小夥伴可以看[RFC2616] 的定義,這邊就不做多餘的展開了。

協商 Cache :

它的特性是 如果強 Cache 沒有 Match 到就會到協商 Cache 來,它會發出 HTTP Request 去問 Server 是否採用 Cache,判斷的依據是 Browser 在 Request Header 中會帶有兩個 Tag,Server 根據這些 Tag,來決定是否使用 Cache,而 Tag 分為 Last-ModifiedE Tag 兩種,會傳給 Server 比對。

1. Last-Modified :

可以在 Respond Header 中找到,用來表示 Client 上的這項 Cache File 最後的修改日期。

長相 :

Last-Modified: Mon, 09 Jul 2018 02:49:54 GMT

範例表示當 Clint 收到上面這個 Respond ,代表該項 Source 在 2018年 7月 9日 2點 49分 54秒之前都可以拿取 Client Cache 來做使用。

如果我們打開 Chrome DevTools 來看的話,就如下圖:

Source

特性 :

  • Browser 在第一次跟 Server 拿某個 Source 的時候, Server會紀錄時間,然後在 Respond Header 中帶上這個 Tag 給 Browser。
  • 第二次之後發送的 Tag Name 會變成 If-Modified-Since,就是上一次 Server Respond 的 Last-Modified String。
  • Server 拿到 Request Header 中的 If-Modified-Since 後,會跟自己所保存的這項 Source 最後修改時間 做比較。
  • 如果 Request Header 的 If-Modified-Since 小於最後修改時間,說明是時候更新了,就會 Respond 更新通知,請 Client 發出 HTTP Request,去拿新版的 Source。
  • 如果 Server 比對後,沒有小於最後修改時間Respond 就返回 304,告訴 Browser 可以直接用 Client Cache。

缺點 :

  • 如果在 Client 打開 Cache File 即便沒有修改,也會造成 Last-Modified 被判定為修改過,導致又發出 HTTP Request。
  • 某些 File 也許會周期性的更改,但是他的內容並沒變(僅僅改變的修改時間),這時候也不用再發出 HTTP Request 去拿新版。
  • Last-Modified 能偵測的時間細度是到秒而已,如果 Cache File 在1 秒內被改了很多次,即使這樣 Last-Modified 並不會判定為修改過。

也因為上面這些缺點,所以在 HTTP 1.1 出現了 E Tag,我們接著來看看吧!!

2. E Tag :

它是 Server 根據當前收到的 Cache File Content,給 File 生成的 Key,一旦裡面的 Content 有變動,這個 Key 就會一起變,只要 Browser 收到變動通知就去拿新版的 Source。

長相 :

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" (它是亂數產生的)
ETag: W/"0815"

範例表示當 Clint 收到上面這個 Respond ,代表該項 Source 比對之後,Key沒變都可以拿取 Client Cache 來做使用。

如果我們打開 Chrome DevTools 來看的話,就如下圖:

Source

特性 :

  • Browser 在第一次拿取該項 Source 時,Server 的 Respond 中就會帶有這個 Tag (Key)。
  • 第二次之後的拿取 Request Header 會帶有一個 Tag Name 叫 If-None-Match,它就是上次 Server 的 Respond 的那個 Key。
  • Server 拿到 Request Header 中的 If-None-Match 後,會跟自己所保存的這項 Source 的 Key 做比較。
  • 如果 Server 比較後 Key 有變,表示有更新了,就會 Respond 更新通知,請 Client 再發出 HTTP Request,去拿新版的 Source。
  • 如果 Server 比對後,Key 沒變 Respond 就返回 304,告訴 Browser 可以直接用 Client Cache。

Additional Tips:

以上 E Tag 的特性算是補足了 Last-Modified 的缺陷部分,看到這裡你也會出現一個疑問這兩者誰的權重比較高呢?

  • E Tag 比較高,若有 E Tag 存在 Browser 會先以它為優先比較

我們來總結一下,當一個 Browser Get Source 發生時,Cache 機制的執行順序吧!

Step 1. Cache-Control (強 Cache)

Step 2. Expires (強 Cache)

Step 3. If-None-Match - E Tag (協商 Cache)

Step 4. If-Modified-Since - Last-Modified (協商 Cache)

Step 5. 都沒 Match 到 or 確認過期後,發出 HTTP Request 拿新版 Source

以上就是 Browser Cache 的第一篇,其實本篇在 Cache 相關的領域中只能算是最初階的入門文,而且文中也有一些細節還沒有說,例如: Status code 的區別、Cache-Control 其他的控制屬性 (private、no-cache、no-store...) 之類的後續會再展開另外一篇來說明,最後希望這篇對大家入門有幫助啦!!

--

--

Huge Gun

槍再大把,沒子彈是不行的;通過學習,鍛造自己的子彈!https://github.com/HsienW