前端面試常問第二大問題是http緩存
相關內容。說真的,http緩存相關的細節比較多,而且 http 經常使用協議版本有1.0、1.1,(本文暫不討論http2.0)。javascript
咱們先羅列一下和緩存相關的請求響應頭。css
響應頭,表明該資源的過時時間。html
請求/響應頭,緩存控制字段,精確控制緩存策略。前端
請求頭,資源最近修改時間,由瀏覽器告訴服務器。java
響應頭,資源最近修改時間,由服務器告訴瀏覽器。webpack
響應頭,資源標識,由服務器告訴瀏覽器。web
請求頭,緩存資源標識,由瀏覽器告訴服務器。面試
配對使用的字段:瀏覽器
今天着重介紹一下瀏覽器緩存機制,咱們知道,瀏覽器緩存通常都是針對靜態資源,好比 js、css、圖片 等,因此咱們下面的例子圍繞一個 javascript 文件 a.js 來闡述。拋開理論式灌輸,咱們從實際場景觸發,一點點完善緩存機制,這種方式,相信你們會更容易理解。緩存
作一些約定,方便之後比較。
執行一個往返,流量爲 10(a.js)+1(請求頭)+1(響應頭) = 12KB。
訪問 10 次,流量大約爲12 * 10 = 120KB。
因此,流量與訪問次數有關:
L(流量) = N(訪問次數) * 12。
該方式缺點很明顯:
js 執行時間相比下載時間要快的多,若是能優化下載時間,用戶體驗會提高不少。
第一次訪問,流量爲 1+10+1 = 12KB。 第二次訪問,流量爲 0。 。。。 第 10000 次訪問,流量依然爲 0。
因此流量與訪問次數無關:
L(流量) = 12KB。
優勢:
缺點:服務器上 a.js 更新時,瀏覽器感知不到,拿不到最新的 js 資源。
服務器和瀏覽器約定文件過時時間,用 Expires 字段來控制,時間是 GMT 格式的標準時間,如 Fri, 01 Jan 1990 00:00:00 GMT。
服務器告訴瀏覽器:你把我發給你的 a.js 文件緩存到你那裏,在 2018年9月26日5點以前不要再發請求煩我,直接使用你本身緩存的 a.js 就好了。
該種方式較以前的方式有了很大的改善:
缺點仍是有:
爲了解決上個方案的問題,服務器和瀏覽器通過磋商,制定了一種方案,服務器每次返回 a.js 的時候,還要告訴瀏覽器 a.js 在服務器上的最近修改時間 Last-Modified (GMT標準格式)。
瀏覽器訪問 a.js 文件。(1KB)
服務器返回 a.js 的時候,告訴瀏覽器 a.js 文件。(10+1=11KB) 在服務器的上次修改時間 Last-Modified(GMT標準格式)以及緩存過時時間 Expires(GMT標準格式)
當 a.js 過時時,瀏覽器帶上 If-Modified-Since(等於上一次請求的Last-Modified) 請求服務器。(1KB)
服務器比較請求頭裏的 Last-Modified 時間和服務器上 a.js的上次修改時間:
此種方案比上一個方案有了更進一步的優化:
缺點:
精確到秒存在兩個問題:
爲了兼容已經實現了上述方案的瀏覽器,同時加入新的緩存方案,服務器除了告訴瀏覽器 Expires ,同時告訴瀏覽器一個相對時間 Cache-Control:max-age=10秒。意思是在10秒之內,使用緩存到瀏覽器的 a.js 資源。
瀏覽器先檢查 Cache-Control,若是有,則以 Cache-Control 爲準,忽略 Expires。若是沒有 Cache-Control,則以 Expires 爲準。
爲了解決文件修改時間只能精確到秒帶來的問題,咱們給服務器引入 Etag 響應頭,a.js 內容變了,Etag 才變。內容不變,Etag 不變,能夠理解爲 Etag 是文件內容的惟一 ID。 同時引入對應的請求頭 If-None-Match,每次瀏覽器請求服務器的時候,都帶上If-None-Match字段,該字段的值就是上次請求 a.js 時,服務器返回給瀏覽器的 Etag。
到此就結束了嗎? 是的,http的緩存機制就是如此了,可是仍然存在一個問題:
瀏覽器沒法主動得知服務器上的 a.js 資源變化了。
無論用 Expires 仍是 Cache-Control,他們都只可以控制緩存是否過時,可是在緩存過時以前,瀏覽器是沒法得知服務器上的資源是否變化的。只有當緩存過時後,瀏覽器纔會發請求詢問服務器。
你們能夠想象咱們使用 a.js 的場景,咱們通常都是輸入網址,訪問一個 html 文件,html文件中會引入 js、css 、圖片等資源。
因此呢,咱們在html上作些手腳。
咱們不讓 html 文件緩存,每次訪問 html 都去請求服務器。因此瀏覽器每次都能拿到最新的html資源。
a.js 內容更新的時候,咱們修改一下 html 中 a.js 的版本號。
<script src="http://test.com/a.js?version=0.0.1"></script>
複製代碼
瀏覽器下載0.0.1版本的a.js文件。
瀏覽器再次訪問 html,發現仍是0.0.1版本的a.js文件,則使用本地緩存。
某一天a.js變了,咱們的html文件也相應變化以下:
<script src="http://test.com/a.js?version=0.0.2"></script>
複製代碼
因此,經過設置html不緩存,html引用資源內容變化則改變資源路徑的方式,就解決了沒法及時得知資源更新的問題。
固然除了以版本號來區分,也能夠以 MD5hash 值來區分。 如
<script src="http://test.com/a.【hash值】.js"></script>
複製代碼
使用webpack打包的話,藉助插件能夠很方便的處理。
Cache-Control 除了能夠設置 max-age 相對過時時間之外,還能夠設置成以下幾種值:
瀏覽器請求服務器時,若是緩存時間沒到,中間服務器直接返回給瀏覽器內容,而沒必要請求源服務器。
瀏覽器請求服務器時,中間服務器都要把瀏覽器的請求透傳給服務器。
每次訪問資源,瀏覽器都要向服務器詢問,若是文件沒變化,服務器只告訴瀏覽器繼續使用緩存(304)。
每次訪問資源,瀏覽器都必須請求服務器,而且,服務器不去檢查文件是否變化,而是直接返回完整的資源。
Cache-Control 對緩存的控制粒度更細,包括緩存代理服務器的緩存控制。
文章介紹到此,若有興趣,能夠動手實踐下。