在讀本文前你要確保讀過我的上篇文章《扼殺 304,Cache-Control: immutable》,因為本文是接著上文寫的。上文說到,在現代 Web 上,“條件請求/304 響應”絕大多數都是浪費資源,因為絕大多數靜態資源都是永恆不變的,因此 Firefox 實現了 Cache-Control: immutable 來讓開發者們控制使用者瀏覽器的重新整理行為,即在重新整理時不要對這些資原始檔進行條件請求(快取不過期的前提下)。
Chrome 至今遲遲沒有實現 Cache-Control: immutable,難道 Chrome 不想讓自己的網頁在重新整理時展現的更快?不是的,其實 Chrome 的開發人員也早已注意到了這個問題。早在 2015 年底,他們就開發出了一種新的重新整理行為,稱之為 new reload/faster reload/non-validating reload,這種新的重新整理和以前傳統的重新整理有個區別,那就是在重新整理頁面時,只對頁面本身的資原始檔進行條件請求(如果快取了的話),頁面裡引用的各種子資原始檔,只要快取不過期,就直接讀取快取。也就是說,“你們開發者壓根不用去加什麼 Cache-Control: immutable 響應頭了,我 Chrome 直接改成重新整理不發條件請求就得了”。
Chrome 在 2015 年底只針對安卓端的 Chrome for Android 中的下拉重新整理開啟了這種新的重新整理,下面是當時的 commit log:
Make pull to refresh not perform regular reload (with cache revalidation)
Pull-to-refresh action on Android is currently handled as a
regular reload, which causes revalidation of cached contents,
however the action would usually imply that the user wants to
refresh the 'contents' rather than reloading everything (e.g.
to reset the page contents when something's screwed up).
這段話中說到,使用者重新整理一個頁面通常都不是為了重新請求每個資原始檔看是不是過期了,而僅僅是為了重置一下頁面的狀態,所以要做這個優化。下面是當時測試效果的對比演示,左舊右新:
可以看到,左側有明顯的載入白屏階段,而右側沒有。
在 16 年 5 月份,Chrome 把該重新整理行為應用到了 PC 端,從 Chrome 54 開始預設啟用。我寫了一個測試 demo,在 Chrome 54 之前,重新整理這個 demo 後效果是這樣的:
所有的資源都會被髮起條件請求從而返回 304 響應,而在 Chrome 54 裡,重新整理這個 demo 後效果成了:
只有頁面本身被髮起了條件請求,所有引用的子資源都直接讀取了快取,也就是說,實際上重新整理這個頁面只觸發了一個 HTTP 請求。
額外小技巧
從 Chrome 56 開始,你可以通過網路皮膚新增的 is:from-cache 過濾條件來過濾出直接讀取快取的請求(即沒有傳送真實的 HTTP 請求的請求),還可以利用它的反向過濾找出傳送了真實請求的請求,像下面這樣(還是上面的 demo):
這個過濾條件是我向 Chrome 提議的。
不知道你們注意過沒有,除重新整理外,常規載入一個網頁的途徑有兩種,最常見的一種是通過點選其他頁面的超連結(包括瀏覽器書籤)進入當前頁面,還有一種是在瀏覽器位址列上輸入網址並回車(高階使用者),這兩者在傳送請求上也是有區別的。前者是不會發起任何條件請求的(除非快取過期),而後者是會為頁面資源本身發起條件請求的(你可以用上面的 demo 做測試),Chrome 新的這個重新整理機制其實就是把重新整理改得和在位址列回車一樣了。鑑於現如今的大多頁面都是動態頁面(沒有 Cache-Control/Expires/Last-modified 響應頭),不會被快取,所以如今這三者在 Chrome 上的差別已經很小了。
如果你已經升級到了 Chrome 的最新版,沒法找到 Chrome 54 之前的版本來做對比測試,可以試試通過停用 chrome://flags/#enable-non-validating-reload-on-normal-reload 選項來切回老版的重新整理,不過這個選項在 Chrome 56 裡已經被徹底刪除了:
讀到這裡,有同學就想問了,“難道 Chrome 這樣做不違反 HTTP 協議嗎?”。並沒有,HTTP 協議只會規定客戶端應該怎麼發一個條件請求,伺服器應該怎麼響應這個條件請求,但不會規定應用程式何時應該傳送條件請求。
那 location.reload() 呢,也會改成新版重新整理嗎?從 Chrome 56 開始 location.reload() 也會使用新版重新整理。HTML 規範並沒有明確的說明 location.reload() 應該如何對待快取,只略略提到了一句:
User agents may allow the user to explicitly override any caches when reloading.
這句話應該是給 Firefox 的 location.reload(true) 準備的。
那對使用者來說,這個改動向後相容嗎?從我注意到 Chrome 的這個改動後,一直以為這是 Chrome 的 bug,但提 bug 後被告知這是有意的。我當時覺的這個改動太激進了,畢竟打破了二十年多年的瀏覽器傳統,一定會有使用者反饋不相容問題的。但直到這個改動進入穩定版後,也沒有一個人反饋。。。彷彿全世界只有我一個人注意到了這個改動。
但對開發者來說,其實這個改動的確是有點不向後相容的。比如某個帶有特定版本號的資源在釋出到 daily 環境後,是可以不換版本號(URL)反覆覆蓋式更新的,但你在瀏覽器裡重新整理卻看不到效果,只能藉助於開發者工具的 Disable cache 功能。不過終端使用者是沒這個問題的,因為線上是無法覆蓋式釋出的。
所以現在我的看法變了,Firefox 和其他瀏覽器應該變的像 Chrome 一樣,Cache-Control: immutable 已經幾乎沒有價值了(除非我漏掉了某個點),能讓所有的網頁變的更快,為什麼不呢。
俗話說的好,“前端有三寶,清快取、清 cookie、換個瀏覽器”。以後在 Chrome 裡單純的重新整理是真的不行了,要教使用者怎麼“強制重新整理/跳過快取重新整理”了。
可以看到自己公司 CDN 日誌的同學們,可以看看最近 304 響應的佔比有沒有大幅減少,不過國內的話也許需要等國產瀏覽器的核心更新後才行。