前端高級進階:網(wǎng)站的緩存控制策略最佳實踐及注意事項
對于一個網(wǎng)站來講,性能關(guān)乎用戶體驗,你在更短的時間內(nèi)打開網(wǎng)站,你將會留住更多的用戶。如果你的頁面十秒才能打開,那再好的用戶交互也是徒然。

緩存控制是網(wǎng)站性能優(yōu)化中至為常見及重要的一環(huán),好的緩存控制,除了使網(wǎng)站在性能方面有所提升,在財務(wù)方面也有重要提升: 更好的緩存策略意味著更少的請求,更少的流量,更少的峰值帶寬,從而節(jié)省一大筆服務(wù)器或者 CDN 的費用。
緩存控制策略就是 http caching 的策略,化繁為簡,最有效的策略往往是很簡單的。在最簡單的粗略下,你對 http cache 只需要了解一個 Cache-Control 的頭部。
一個較好的緩存策略只需要兩部分,而它們只需要通過 Cache-Control 控制:
- 帶指紋資源: 永久緩存
- 非帶指紋資源: 每次進行新鮮度校驗
作圖如下:
緩存控制策略帶指紋資源: 永久緩存
Cache-Control: max-age=31536000
天下武功,無堅不摧,唯快不破。資源請求最快的方式就是不向服務(wù)器發(fā)起請求,通過以上響應(yīng)頭可以對資源設(shè)置永久緩存。
- 靜態(tài)資源帶有 hash 值,即指紋
- 對資源設(shè)置一年過期時間,即 31536000,一般認為是永久緩存
- 在永久緩存期間瀏覽器不需要向服務(wù)器發(fā)送請求
那為什么帶有 hash 值的資源可以永久緩存呢?
因為該文件的內(nèi)容發(fā)生變化時,會生成一個帶有新的 hash 值的 URL。 前端將會發(fā)起一個新的 URL 的請求。
非帶指紋資源: 每次進行新鮮度校驗
Cache-Control: no-cache
- 由于不帶有指紋,每次都需要校驗資源的新鮮度。(從緩存中取到資源,可能是過期資源)
- 如果校驗為最新資源,則從瀏覽器的緩存中加載資源
index.html 為不帶有指紋資源,如果把它置于緩存中,則如何保證服務(wù)器刷新數(shù)據(jù)時,被瀏覽器可以獲取到新鮮的資源?
因此,使用 Cache-Control: no-cache 時,客戶端每次對服務(wù)器進行新鮮度校驗。
PS:no-cache 與 no-store 的區(qū)別是什么?
即使每次校驗新鮮度,也不需要每次都從服務(wù)器下載資源: 如果瀏覽器/CDN上緩存經(jīng)校驗沒有過期。這被稱為協(xié)商緩存,此時 http 狀態(tài)碼返回 304,指 Not Modified,即沒有變更。
幸運的是,關(guān)于協(xié)商緩存,你無需管理,也無需配置, nginx 或者一些 OSS 都會自動配置協(xié)商緩存。
而對于協(xié)商緩存,也有它們自己的算法,協(xié)商緩存的背后基于響應(yīng)頭 Last-Modified/ETag。瀏覽器每次請求資源時,會攜帶上次服務(wù)器響應(yīng)的 ETag/Last-Modified 作為標志,與服務(wù)端此時的 ETag/Last-Modified 作比較,來判斷內(nèi)容更改。
http 響應(yīng)頭中的 ETag 值是如何生成的?
而在操作系統(tǒng)底層,Last-Modified 往往通過文件系統(tǒng)(file system)中的 mtime 屬性生成。而 ETag 提供比 Last-Modified 更精細的檢驗粒度,由文件內(nèi)容的 hash 或者 mtime/size 生成。當然,這是后話。
一定要為你的資源添加 Cache-Control 響應(yīng)頭
我會經(jīng)常接觸到一些網(wǎng)站,他們的資源文件并沒有 Cache-Control 這個響應(yīng)頭。究其原因,在于緩存策略配置這個工作的職責不清,有時候它需要協(xié)調(diào)前端和運維。
那如果不添加 Cache-Control 這個響應(yīng)頭會怎么樣?
是不是每次都會自動去服務(wù)器校驗新鮮度,很可惜,不是。此時會對資源進行強制緩存,而對不帶有指紋信息的資源很有可能獲取到過期資源。 如果過期資源存在于瀏覽器上,還可以通過強制刷新瀏覽器來獲取最新資源。但是如果過期資源存在于 CDN 的邊緣節(jié)點上,CDN 的刷新就會復(fù)雜很多,而且有可能需要多人協(xié)作解決。
那默認的強制緩存時間是多少
首先要明確兩個響應(yīng)頭代表的含義:
Date: 指源服務(wù)器響應(yīng)報文生成的時間,差不多與發(fā)請求的時間等價Last-Modified: 指靜態(tài)資源上次修改的時間,取決于mtime
LM factor 算法認為當請求服務(wù)器時,如果沒有設(shè)置 Cache-Control,如果距離上次的 Last-Modified 越遠,則生成的強制緩存時間越長。
用公式表示如下,其中 factor 介于 0 與 1 之間:
MaxAge = (Date - LastModified) * factor
LM factorBundle Splitting:盡量減少資源變更
得益于單頁應(yīng)用與前端工程化的發(fā)展,經(jīng)過打包后,基本上所有資源都是帶有指紋信息的,這意味著所有的資源都是能夠設(shè)置永久緩存。打包策略如下圖所示:
緩存控制策略但僅僅如此了嗎?
如果你所有的 js 資源都打包成一個文件,它確實有永久緩存的優(yōu)勢。但是當有一行文件進行修改時,這一個大包的指紋信息發(fā)生改變,永久緩存失效。
所以我們現(xiàn)在需要做到的是:當修改文件后,造成最小范圍的緩存失效。webpack 等打包工具雖然在 optimization 上內(nèi)置了很多性能優(yōu)化,但它不會幫你做這件事,這件事情需要自己動手。
緩存控制策略此時我們可以對資源進行分層次緩存的打包方案,這是一個建議方案:
webpack-runtime: 應(yīng)用中的webpack的版本比較穩(wěn)定,分離出來,保證長久的永久緩存react/react-dom:react的版本更新頻次也較低vendor: 常用的第三方模塊打包在一起,如lodash,classnames基本上每個頁面都會引用到,但是它們的更新頻率會更高一些。另外對低頻次使用的第三方模塊不要打進來pageA: A 頁面,當 A 頁面的組件發(fā)生變更后,它的緩存將會失效pageB: B 頁面echarts: 不常用且過大的第三方模塊單獨打包mathjax: 不常用且過大的第三方模塊單獨打包jspdf: 不常用且過大的第三方模塊單獨打包
隨著 http2 的發(fā)展,特別是多路復(fù)用,初始頁面的靜態(tài)資源不受資源數(shù)量的影響。因此為了更好的緩存效果以及按需加載,也有很多方案建議把所有的第三方模塊進行單模塊打包。
小結(jié)
緩存控制策略本篇文章地址在 前端工程化系列,歡迎訂閱。
推薦閱讀
我的公眾號能帶來什么價值?(文末有送書規(guī)則,一定要看)
每個前端工程師都應(yīng)該了解的圖片知識(長文建議收藏)
為什么現(xiàn)在面試總是面試造火箭?
