網絡中數據傳輸是很耗時的,數據要在漫長的路徑中奔波,客戶端在數據完整到達前只能等待。如果能夠復用已經請求過的資源,勢必會讓整個頁面加載高效許多。這可以通過合理地設置服務器的緩存,與瀏覽器的緩存機制配合以達到最優。
緩存設置得當不但可減少用戶等待時間,提升體驗,還節省服務器開銷省流量帶寬。
緩存的配置有兩種策略:
- 穩定的內容 + 長期緩存
- 經常變動的內容 + 使用前詢問
穩定的內容 + 長期緩存
在知道文件內容不太可能變化的情況下,可對該資源進行長期緩存。
Cache-Control: max-age=31536000
這種模式下瀏覽器獲取資源流程如下:
- 頁面:請求資源
a.v1.js
,b.v1.css
。 - 緩存:本地沒有,向服務器獲取。
- 服務器:找到資源並返回,同時告知瀏覽器緩存該資源,比如,緩存一年。
- 頁面:一段時間后再次請求
a.v2.js
,b.v1.css
。 - 緩存:發現本地有對
b.v1.css
的緩存,直接使用,對於a.v2.js
則詢問服務器。 - 服務器:找到資源並返回
a.v2.js
,同時告知瀏覽器緩存該資源。
可以看到,這種模式下,我們更新的是文件名,即資源的 URI 地址,而不是直接更新文件內容。因為文件被緩存后,如果文件名沒變,瀏覽器是不會重新去獲取的。
經常變動的內容 + 使用前詢問
對於經常變動的資源,但地址又不能變,比如靜態博客頁面,則不能像上面那樣緩存。這種情況下可設置緩存為 no-cache
。
Cache-Control: no-cache
需要注意的是,緩存 Header 的值不能按照字面意思來解釋,需要去理解它,比如:
no-cache
並不是表示不要緩存,而是緩存該資源,但使用前先詢問服務器該資源是否有更新,而no-store
才表示完全不緩存。must-revalidate
不是必需重新驗證資源有效性的意思,而是暗含了一個前提,就是資源如果還沒有超過設置的緩存時限max-age
才重新驗證。
此模式下,服務器可通過下發 ETag
或 Last-Modified
響應頭,瀏覽器下次再請求時會查檢查已緩存的資源,並帶上相應的 If-None-Match
或 If-Modified-Since
請求頭,然后服務器再決定是否返回新的資源或告知瀏覽器直接使用本地緩存。
使用 ETag 的場景示例:
- 瀏覽器請求資源。
- 服務器返回資源,並且帶上 ETag (可以是 hash 或者其他能夠跟隨資源內容而變的 id)。
- 過段時間,瀏覽器再次請求該資源,通過設置
If-None-Match
請求頭帶上前面得到的 ETag。 - 服務器將 ETag 與資源內容進行比較,發現資源沒有更新過,返回 304 (not modified)告訴瀏覽器資源沒有變化,可使用本地已經緩存的版本。
- 瀏覽器得到 304 響應,直接使用本地緩存。
整個過程沒有對資源進行重復下載。
ETag
/Last-Modified
不可用的情況下,服務器始終下發完整資源。
相比方式一,這種方式始終會和服務器進行一次溝通。
max-age
的注意事項
對易變的內容設置 max-age
方式的緩存容易引起各資源不一致的問題。
比如設置緩存為如下格式時,
Cache-Control: must-revalidate, max-age=600
對於緩存時間小於 10 分鍾的資源,瀏覽器不會重新請求而是直接使用緩存。
假設一個場景,頁面 A 包含一個公共腳本 common.js
和頁面 A 的業務腳本 a.js
。當頁面 A 首頁加載時,所有資源都正確緩存。
過了一段時間,切換到頁面 B,頁面 B 也包含公共腳本 common.js
,同時有自己的業務腳本 b.js
。
在請求頁面 B 之前,因為已經緩存過 common.js
,所以會使用緩存,但這期間文件有可能已經更新。此時瀏覽器使用舊的 common.js
運行頁面 B 勢必會出問題。
所以,對於經常變動的內容設置 max-age
是不推薦的做法。
多數情況下針對上面的問題,一次強刷就解決了,這也是有 bug 時研發會給出的高頻回復。