什么是前端缓存
大家在日常的開發工作過程中,有沒有遇到過下面幾種情況:
- 部署/發布前端工程后,增加的功能或修改的bug沒有生效
- 測試同學測試功能時經常暴力地清除所有瀏覽器緩存
- 前端開發同學經常說:你“強刷”一下就好了
- …
遇到上面這些情況,大部分同學就知道了,這是前端有緩存的原因,那具體什么是前端緩存呢,前端緩存僅僅和前端有關系嗎?
前端緩存 / 瀏覽器緩存
前端緩存,是瀏覽器為了提升網站的加載性能,縮短用戶等待時間而采取的措施,瀏覽器總是想盡量少地向服務器發送請求,能夠從自己保存的副本中得到的,就不去麻煩服務器了,畢竟自己動手豐衣足食嘛,所以更準確的叫法應該為瀏覽器緩存,下文中如果出現緩存等字眼,指的就是前端緩存或瀏覽器緩存。
由上所述,緩存對于用戶來說是友好的,而且對大多數用戶透明的,普通用戶可能最多只是感覺再次進入一個網站時速度變快了而已,再進一步可能某些用戶發現一些靜態頁面斷網后還能被訪問。但是對于開發同學就需要對緩存有所了解,并在發布新版時特別注意。
緩存機制
那緩存是具體是什么呢?我們可以將其理解為我們下載到硬盤的文件,比如說老師為我們制作了一份課件,我們將其下載放在了硬盤中,那之后我們什么時候想看,只需要到這份課件保存的文件夾內,將其打開就好了,而不必再次下載了。但是有一天老師發現了課件里面有一些問題,那老師為了不誤導我們肯定想要修正這些問題,修正過后呢,老師在上課的時候就告訴我們“同學們,你們上次下載的課件有些問題,我已經改了,你們再重新下載一下,之前那份就不要看了”。那我們回去之后就重新下載老師修改過后的課件,之前的課件就再也不需要看了。
明白了上面的例子,其實也就明白了緩存的機制,對應于緩存,工作流程應該是這樣的:
用戶小U使用瀏覽器小B訪問了一個頁面P,瀏覽器將頁面P和其中包含的資源文件(一般包含css、js、圖片等文件)保存下來了,一段時間內呢小B反復訪問頁面P,小B都會從自己的存儲區取出相應文件為小U渲染出同樣的頁面P。然后有一天頁面P更新了,比如說主題變了或者里面的按鈕功能變化了,當小B再次訪問頁面P時,它知道自己之前保存的頁面P的資源文件已經過時了,然后就再次訪問一下頁面P,再將頁面P和其中包含的資源文件保存下來,之前保存的頁面P和其中包含的資源文件就沒用了。
那上面兩個流程是不是都有個重新下載的過程,那重要的就是如果感知到資源的變化。第一個例子中是老師通知同學們資源變化了,第二個例子就沒有上面說的那么簡單了,沒人主動告訴小B它緩存的資源已經過時了,那小B是通過什么方式才能知道自己緩存的資源已經過時了呢:
緩存的狀態
| 無緩存 | 初次見面,第一次進入某個頁面 |
| 緩存過時/非法 | 重逢不識君,進入過某個頁面,并有對應緩存,但再次進入時可能已經過時了 |
| 緩存合法 | 歸來仍是少年,進入過某個頁面,并有對應緩存,再次進入時仍然是有效的 |
而緩存狀態的變化,需要瀏覽器和服務器之間達成某些協議,這些協議由HTTP頭部確定及執行,這里我們介紹最常用的:
緩存頭部
- Cache-Control
- Etag及If-None-Match夫婦
- Last-Modified及If-Modified-Since夫婦
Cache-Control
Cache-Control的語法及使用姿勢眾多,剛興趣的可直接到MDN查看,這里我們只介紹它在響應頭中用于告知瀏覽器如何進行緩存行為:
Response HeadersCache-Control: public, max-age=31536000上面的響應頭中Cache-Control: public, max-age=31536000告知瀏覽器從當前請求的時間點開始,再次請求此資源如果還未超過31536000秒(1年),你就就不必問我了,放心地使用你本地保存的就好了。但是這就有個問題了,如果一年內的某天,此資源變化了,瀏覽器該如何知道呢?很遺憾,這種情況下瀏覽器在1年內是不會再知道了。
所以如果web server想要對資源設置諸如Cache-Control: public, max-age=31536000響應頭,一般需要前端搭配文件名hash來使用,這在各種構建工具或腳手架中一般都有相應的配置,如webpack配置:
// webpack module.exports = {// ...output: {filename: '[name].[contenthash].js',// filename: '[name].[hash].js',// ...},optimization: {moduleIds: 'hashed',// ...},// ... }適用資源:基本所有的資源型文件(如js、css、圖片、字體文件等)。
設置為1年沒有什么其他含義,只是一個較大的時間區間而已。
當我們按照上面方式配置完成后,滿心歡喜的去發布新版了,可是部署完成后再次訪問,尷尬了,沒有變化?這就要注意了,緩存對于html文件也是生效的,我知道這很顯而易見,但是很多人容易忽略。
特別是對于單頁面應用來說,我們一般只有一個index.html,在index.html中引入其他js、css等資源文件,上面的步驟只是對于index.html中引入的資源文件名中添加了hash,保證發布新版后這些引入的資源文件在瀏覽器緩存中不存在,但是如果瀏覽器取得index.html是通過本地緩存得到的呢?
<!-- 緩存內index.html --> <link rel="stylesheet" href="index.v1.css" /> <script src="index.v1.js"></script> <!-- 新版的index.html --> <link rel="stylesheet" href="index.v2.css" /> <script src="index.v2.js"></script>這里為了方便,我們使用v1、v2等代指hash。
很顯然,如果瀏覽器從緩存中獲取index.html,然后肯定會嘗試獲取index.v1.css和index.v1.js,而這兩個文件再緩存中也是存在的且是合法的(假設還在1年內),那自然用戶看到的頁面及功能都是老的了。那我們應該如何為html文件設置緩存策略呢?
我們可以在web server的配置中針對html文件設置Cache-Control: no-cache,當我們請求html文件時,響應頭會包含:
Response HeadersCache-Control: no-cache要特別注意no-cache(允許緩存,但是使用前要向服務器確定緩存是否合法,確定方案下面會講到)和no-store(不允許緩存)的區別。
這樣的話,當我們發布新版后,瀏覽器請求index.html發現有緩存,但并不會無腦的使用,而是會向服務器確認一下當前的緩存是否合法,如果合法則直接使用緩存內的版本,否則會向服務器請求最新的index.html,之后我們的index.v2.css和index.v2.js就能夠被正確獲取,用戶就能夠看到新的頁面了。
ETag及If-None-Match
ETag被稱為實體標簽或版本標識符,ETag變化代表資源的變化。
上面說道,瀏覽器有時需要向服務器確定緩存是否合法,那通過ETag響應頭部和If-None-Match請求頭部就能夠確定,大致過程如下:
v1、v2只是為了標識出ETag的變化,實際上生成ETag的算法也不唯一,甚至簡單地使用版本號也可以。關于ETag詳細信息可參閱MDN。
Last-Modified及If-Modified-Since
除了ETag及If-None-Match,使用Last-Modified和If-Modified-Since組合也能起到類似的效果,大致過程和前組合類似:
Last-Modified和If-Modified-Since組合準確度不如ETag及If-None-Match組合,所以不太推薦使用:
- 有些服務器無法正確地判斷資源的最近修改日期
- 如果資源的變化周期在秒級以下,只能精確到秒的修改日期就不那么精確了
確定緩存是否合法,也會發請求和服務器端通信,但如果緩存有效,服務器發回的響應中是不包含響應體的,這樣流量消耗是很小的,只有頭部的消耗;即使緩存無效,也只是相當于發了一個首次請求而已。
總結
綜上所述,我們可以在部署前端工程時使用如下方案,保證用戶能夠享受緩存帶來的便利,也能保證不會因為緩存造成更新不生效的問題:
- 針對大部分資源文件,使用Cache-Control: public, max-age=31536000及文件名hash的方案
- 針對html文件,使用Cache-Control: no-cache和ETag方案
為不同類型資源配置響應頭部是web server的工作,請求頭部是瀏覽器的自發工作,文件hash是前端的工作。
總結
- 上一篇: 为什么使用multiarmed band
- 下一篇: 搜狗浏览器如何清除浏览器缓存--小白