緩存好處有不少:javascript
🤦♀️那麼問題又來了,既然緩存這麼好,若是我請求的服務器中間有代理也緩存了怎麼辦?代理服務器緩存了個人資源致使我無法從源服務器拿到最新的資源怎麼辦?HTTP固然也想到了這塊的訴求。接下來咱們也會逐層剖析。css
🍉緩存在宏觀上能夠分紅兩類:私有緩存和共享緩存。共享緩存就是那些能被各級代理緩存的緩存(咋以爲有點繞)。私有緩存就是用戶專享的,各級代理不能緩存的緩存。html
🐜微觀上能夠分下面三類:java
我相信只要你常用某個瀏覽器🌎(Chrome,Firefox,IE等),確定知道這些瀏覽器在設置裏面都是有個清除緩存功能,這個功能存在的做用就是刪除存儲在你本地磁盤上資源副本,也就是清除緩存。node
緩存存在的意義就是當用戶點擊back按鈕或是再次去訪問某個頁面的時候可以更快的響應。尤爲是在多頁應用的網站中,若是你在多個頁面使用了一張相同的圖片,那麼緩存這張圖片就變得特別的有用。😏jquery
代理服務器緩存原理和瀏覽器端相似,但規模要大得多,由於是爲成千上萬的用戶提供緩存機制,大公司和大型的ISP提供商一般會將它們設立在防火牆上或是做爲一個獨立的設備來運營。(下文若是沒有特殊說明,全部提到的緩存服務器都是指代理服務器。)web
因爲緩存服務器不是客戶端或是源服務器的一部分,它們存在於網絡中,請求路由必須通過它們纔會生效,因此實際上你能夠去手動設置瀏覽器的代理,或是經過一箇中間服務器來進行轉發,這樣用戶天然就察覺不到代理服務器的存在了。🤥算法
代理服務器緩存就是一個共享緩存,不僅爲一個用戶服務,常常爲大量用戶使用,所以在減小相應時間和帶寬使用方面頗有效:由於同一個緩存可能會被重用屢次。chrome
也被稱爲代理緩存或反向代理緩存,網關也是一箇中間服務器,網關緩存通常是網站管理員本身部署,從讓網站擁有更好的性能。🙂數據庫
CDNS(網絡內容分發商)分佈網關緩存到整個(或部分)互聯網上,並出售緩存服務給須要的網站,好比國內的七牛雲、又拍雲都有這種服務。
數據庫緩存是指當咱們的應用極其複雜,表天然也很繁雜,咱們必須進行頻繁的進行數據庫查詢,這樣可能致使數據庫不堪重負,一個好的辦法就是將查詢後的數據放到內存中,下一次查詢直接從內存中取就行了。關於數據庫緩存本篇不會展開。🙃
緩存的目標:
以上,對於咱們能夠和應該緩存的目標有個瞭解。🤗
瀏覽器對於緩存的處理是根據第一次請求資源時返回的響應頭來肯定的。
那麼瀏覽器怎麼肯定一個資源該不應緩存,如何去緩存呢❓響應頭!響應頭!響應頭!重要的事情說三遍。✌️
咱們看🌰:
Age:23146
Cache-Control:max-age=2592000
Date:Tue, 28 Nov 2017 12:26:41 GMT
ETag:W/"5a1cf09a-63c6" Expires:Thu, 28 Dec 2017 05:27:45 GMT Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT Vary:Accept-Encoding
以上請求頭來自百度首頁某個CSS文件的響應頭。我去除了一些和緩存無關的字段,只保留了以上部分。咱們來分析下,Expires
是HTTP/1.0中的定義緩存的字段,它規定了緩存過時的一個絕對時間。Cache-Control:max-age=2592000
是HTTP/1.1定義的關於緩存的字段,它規定了緩存過時的一個相對時間。優先級上固然是版本高的優先了,max-age > Expires
。
這就是強緩存階段,當瀏覽器再次試圖訪問這個CSS文件,發現有這個文件的緩存,那麼就判斷根據上一次的響應判斷是否過時,若是沒過時,使用緩存。加載文件,OVER!✌️
Firefox瀏覽器表現爲一個灰色的200狀態碼。
Chrome瀏覽器狀態碼錶現爲:
200 (from disk cache)或是200 OK (from memory cache)
**多說一點:**關於緩存是從磁盤中獲取仍是從內存中獲取,查找了不少資料,得出了一個較爲可信的結論:Chrome會根據本地內存的使用率來決定緩存存放在哪,若是內存使用率很高,放在磁盤裏面,內存的使用率很高會暫時放在內存裏面。這就能夠比較合理的解釋了爲何同一個資源有時是from memory cache有時是from disk cache的問題了。
那麼當這個CSS文件過時了怎麼辦?ETag
和Last-Modified
就該閃亮登場了。
先說Last-Modified
,這個字段是文件最後一次修改的時間;
ETag
呢?ETag是對文件的一個標記,嗯,能夠這麼說,具體生成方式HTTP並無給出一個明確的方式,因此理論上只要不會重複生成方式無所謂,好比對資源內容使用抗碰撞散列函數,使用最近修改的時間戳的哈希值,甚至只是一個版本號。
####2. 協商緩存階段
利用這兩個字段瀏覽器能夠進入協商緩存階段,當瀏覽器再次試圖訪問這個CSS文件,發現緩存過時,因而會在本次請求的請求頭裏攜帶If-Moified-Since
和If-None-Match
這兩個字段,服務器經過這兩個字段來判斷資源是否有修改,若是有修改則返回狀態碼200和新的內容,若是沒有修改返回狀態碼304,瀏覽器收到200狀態碼,該咋處理就咋處理(至關於首次訪問這個文件了),發現返回304,因而知道了本地緩存雖然過時但仍然能夠用,因而加載本地緩存。而後根據新的返回的響應頭來設置緩存。(這一步有所差別,發現不一樣瀏覽器的處理是不一樣的,chrome會爲304設置緩存,firefox則不會)😑
具體兩個字段攜帶的內容以下(分別和上面的Last-Modified
、ETag
攜帶的值對應):
If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMT
If-None-Match: W/"5a1cf09a-63c6"
到這協商緩存結束。
咱們把上面的響應頭改下:
Age:23146 Cache-Control: public Date:Tue, 28 Nov 2017 12:26:41 GMT Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT Vary:Accept-Encoding
發現沒?瀏覽器用來肯定緩存過時時間的字段一個都沒有!那該怎麼辦?有人可能會說下次請求直接進入協商緩存階段,攜帶If-Moified-Since
唄,不是的,瀏覽器還有個啓發式緩存階段😎
根據響應頭中2個時間字段 Date 和 Last-Modified 之間的時間差值,取其值的10%做爲緩存時間週期。
這就是啓發式緩存階段。這個階段很容讓人忽視,但實際上每時每刻都在發揮着做用。因此在從此的開發過程當中若是遇到那種默認緩存
的坑,不要叫囂,不要生氣,瀏覽器只是在遵循啓發式緩存協議而已。
我畫了下面這張圖,來解釋瀏覽器整個緩存策略的過程:
👌對於緩存策略介紹到這,接下來再細細分析不一樣的HTTP首部字段的內容,以及它們之間的關係。
HTTP報文是什麼呢?就是HTTP報文,這是一個概念,主要由如下兩部分構成:
以上咱們知道瀏覽器對於緩存的處理過程,也簡單的提到了幾個相關的字段。🤧接下來咱們具體看下這幾個字段:
字段名稱 | 說明 |
---|---|
Cache-Control | 控制緩存具體的行爲 |
Pragma | HTTP1.0時的遺留字段,當值爲"no-cache"時強制驗證緩存 |
Date | 建立報文的日期時間(啓發式緩存階段會用到這個字段) |
字段名稱 | 說明 |
---|---|
ETag | 服務器生成資源的惟一標識 |
Vary | 代理服務器緩存的管理信息 |
Age | 資源在緩存代理中存貯的時長(取決於max-age和s-maxage的大小) |
字段名稱 | 說明 |
---|---|
If-Match | 條件請求,攜帶上一次請求中資源的ETag,服務器根據這個字段判斷文件是否有新的修改 |
If-None-Match | 和If-Match做用相反,服務器根據這個字段判斷文件是否有新的修改 |
If-Modified-Since | 比較資源先後兩次訪問最後的修改時間是否一致 |
If-Unmodified-Since | 比較資源先後兩次訪問最後的修改時間是否一致 |
字段名稱 | 說明 |
---|---|
Expires | 告知客戶端資源緩存失效的絕對時間 |
Last-Modified | 資源最後一次修改的時間 |
HTTP/1.1一共規範了47種首部字段,而和緩存相關的就有以上12個之多。接下來的兩個小節會一個一個介紹給你們。🤓
經過cache-control的指令能夠控制告訴客戶端或是服務器如何處理緩存。這也是11個字段中指令最多的一個,咱們先來看看請求指令:
指令 | 參數 | 說明 |
---|---|---|
no-cache | 無 | 強制源服務器再次驗證 |
no-store | 無 | 不緩存請求或是響應的任何內容 |
max-age=[秒] | 緩存時長,單位是秒 | 緩存的時長,也是響應的最大的Age值 |
min-fresh=[秒] | 必需 | 指望在指定時間內響應仍然有效 |
no-transform | 無 | 代理不可更改媒體類型 |
only-if-cached | 無 | 從緩存獲取 |
cache-extension | - | 新的指令標記(token) |
響應指令:
指令 | 參數 | 說明 |
---|---|---|
public | 無 | 任意一方都能緩存該資源(客戶端、代理服務器等) |
private | 可省略 | 只能特定用戶緩存該資源 |
no-cache | 可省略 | 緩存前必須先確認其有效性 |
no-store | 無 | 不緩存請求或響應的任何內容 |
no-transform | 無 | 代理不可更改媒體類型 |
must-revalidate | 無 | 可緩存但必須再向源服務器進確認 |
proxy-revalidate | 無 | 要求中間緩存服務器對緩存的響應有效性再進行確認 |
max-age=[秒] | 緩存時長,單位是秒 | 緩存的時長,也是響應的最大的Age值 |
s-maxage=[秒] | 必需 | 公共緩存服務器響應的最大Age值 |
cache-extension | - | 新指令標記(token |
請注意no-cache指令不少人誤覺得是不緩存,這是不許確的,no-cache的意思是能夠緩存,但每次用應該去想服務器驗證緩存是否可用。no-store纔是不緩存內容。另外部分指令也能夠組合使用,好比:
Cache-Control: max-age=100, must-revalidate, public
上面指令的意思是緩存的有效時間爲100秒,以後訪問須要向源服務器發送請求驗證,此緩存可被代理服務器和客戶端緩存。
這是HTTP/1.0裏面的一個字段,但優先級很高,測試發現,Chrome和Firefox中Pragma的優先級高於Cache-Control和Expires,爲了向下兼容,這個字段依然發揮着它的做用。🤔通常可能咱們會這麼用:
<meta http-equiv="Pragma" content="no-cache">
Pragma屬於通用首部字段,在客戶端上使用時,常規要求咱們往html上加上上面這段meta元標籤(並且可能還得作些hack放到body後面去。
事實上這種禁用緩存的形式用處頗有限:
Cache-Control: no-store
的meta標籤(見出處)讀者能夠自行拷貝後面模擬服務端決策的代碼進行測試。
服務端響應添加'Pragma': 'no-cache'
,瀏覽器表現行爲和強制刷新相似。
這又是一個HTTP/1.0的字段,上面也說過了定義的是緩存到期的絕對時間。
一樣,咱們也能夠在html文件裏直接使用:
<meta http-equiv="expires" content="Thu, 30 Nov 2017 11:17:26 GMT">
若是設置的是已通過去的時間會怎樣呢?YES!!!則刷新頁面會從新發送請求。
**Pragma禁用緩存,若是又給Expires定義一個還未到期的時間,那麼Pragma字段的優先級會更高。**🤖
🤖Expires有一個很大的弊端,就是它返回的是服務器的時間,但判斷的時候用的倒是客戶端的時間,這就致使Expires很被動,由於用戶有可能改變客戶端的時間,致使緩存時間判斷出錯,這也是引入Cache-Control:max-age
指令的緣由之一。
接下來這幾個字段都是校驗字段,或者說是在協商緩存階段發揮做用的字段。第一個就是Last-modified,這個字段不光協商緩存起做用,在啓發式緩存階段一樣起到相當重要的做用。
在瀏覽器第一次請求某一個URL時,服務器端的返回狀態碼會是200,響應的實體內容是客戶端請求的資源,同時有一個Last-Modified
的屬性標記此文件在服務器端最後被修改的時間。like this:
Last-Modified : Fri , 12 May 2006 18:53:33 GMT
當瀏覽器第二次請求這個URL的時候,根據HTTP協議規定,瀏覽器會把第一次Last-Modified
的值存儲在If-Modified-Since
裏面發送給服務端來驗證資源有沒有修改。like this:
If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
服務端經過If-Modified-Since
字段來判斷在這兩次訪問期間資源有沒有被修改過,從而決定是否返回完整的資源。若是有修改正常返回資源,狀態碼200,若是沒有修改只返回響應頭,狀態碼304,告知瀏覽器資源的本地緩存還可用。
用途:
這個字段字面意思和If-Modified-Since
相反,但處理方式並非相反的。若是文件在兩次訪問期間沒有被修改則返回200和資源,若是文件修改了則返回狀態碼412(預處理錯誤)。
用途:
If-Range
消息頭的範圍請求搭配使用,實現斷點續傳的功能,即若是資源沒修改繼續下載,若是資源修改了,續傳的意義就沒有了。😈Last-Modified
有幾個缺點:無法準確的判斷資源是否真的修改了,好比某個文件在1秒內頻繁更改了屢次,根據Last-Modified的時間(單位是秒)是判斷不出來的,再好比,某個資源只是修改了,但實際內容並無發生變化,Last-Modified也沒法判斷出來,所以在HTTP/1.1中還推出了ETag
這個字段👇
服務器能夠經過某種自定的算法對資源生成一個惟一的標識(好比md5標識),而後在瀏覽器第一次請求某一個URL時把這個標識放到響應頭傳到客戶端。服務器端的返回狀態會是200。
ETag: abc-123456
ETag的值有可能包含一個 W/ 前綴,來提示應該採用弱比較算法(這個是多此一舉,由於 If-None-Match 用且僅用這一算法)。🙄
If-None-Match和If-Modified-Since同時存在的時候If-None-Match優先級更高。
當瀏覽器第二次請求這個URL的時候,根據HTTP協議規定,瀏覽器回把第一次ETag的值存儲在If-None-Match裏面發送給服務端來驗證資源有沒有修改。like this:
If-None-Match: abc-123456
Get請求中,當且僅當服務器上沒有任何資源的ETag屬性值與這個首部中列出的相匹配的時候,服務器端會才返回所請求的資源,響應碼爲200。若是沒有資源的ETag值相匹配,那麼返回304狀態碼。
POST、PUT等請求改變文件的請求,若是沒有資源的ETag值相匹配,那麼返回412狀態碼。
在請求方法爲 GET) 和 HEAD的狀況下,服務器僅在請求的資源知足此首部列出的 ETag
之一時纔會返回資源。而對於 PUT或其餘非安全方法來講,只有在知足條件的狀況下才能夠將資源上傳。
用途:
If-Match
首部能夠用來避免更新丟失問題。它能夠用來檢測用戶想要上傳的不會覆蓋獲取原始資源以後作出的更新。若是請求的條件不知足,那麼須要返回412(預處理錯誤) 響應。固然和Last-Modified
相比,ETag也有本身的缺點,好比因爲須要對資源進行生成標識,性能方面就勢必有所犧牲。😕
關於強校驗和弱校驗:
ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
---|---|---|---|
W/"1" | W/"1" | no match | match |
W/"1" | W/"2" | no match | no match |
W/"1" | "1" | no match | match |
"1" | "1" | match | match |
當Expires
和Cache-Control:max-age=xxx
同時存在的時候取決於緩存服務器應用的HTTP版本。應用HTTP/1.1版本的服務器會優先處理max-age,忽略Expires,而應用HTTP/1.0版本的緩存服務器則會優先處理Expires而忽略max-age。接下來看下和緩存服務器相關的兩個字段。
Vary用來作什麼的呢?試想這麼一個場景:在某個網頁中網站提供給移動端的內容是不一樣的,怎麼讓緩存服務器區分移動端和PC端呢?不知道你是否注意,瀏覽器在每次請求都會攜帶UA字段來代表來源,因此咱們能夠利用User-Agent
字段來區分不一樣的客戶端,用法以下:
Vary: User-Agent
再好比,源服務器啓用了gzip壓縮,但用戶使用了比較舊的瀏覽器,不支持壓縮,緩存服務器如何返回?就能夠這麼設定:
Vary: Accept-Encoding
固然,也能夠這麼用:
Vary: User-Agent, Accept-Encoding
這意味着緩存服務器會以User-Agent
和 Accept-Encoding
兩個請求首部字段來區分緩存版本。根據請求頭裏的這兩個字段來決定返回給客戶端什麼內容。
這個字段說的是資源在緩存服務器存在的時長,前面也說了Cache-Control: max-age=[秒]
就是Age的最大值。
這個字段存在的意義是什麼呢?用來區分請求的資源來自源服務器仍是緩存服務器的緩存的。
🤧但得結合另外一個字段來進行判斷,就是Date,Date是報文建立的時間。
若是按F5頻繁刷新發現響應裏的Date沒有改變,就說明命中了緩存服務器的緩存如下面的一個響應爲🍐:
Accept-Ranges: bytes
Age: 1016859
Cache-Control: max-age=2592000
Content-Length: 14119
Content-Type: image/png
Date: Fri, 01 Dec 2017 12:27:25 GMT
ETag: "5912bfd0-3727" Expires: Tue, 19 Dec 2017 17:59:46 GMT Last-Modified: Wed, 10 May 2017 07:22:56 GMT Ohc-Response-Time: 1 0 0 0 0 0 Server: bfe/1.0.8.13-sslpool-patch
如上圖來自百度首頁某個圖片的響應字段。咱們能夠看到Age=1016859,說明這個資源已經在緩存服務器存在了1016859秒。若是文件被修改或替換,Age會從新由0開始累計。
Age消息頭的值一般接近於0。表示此消息對象剛剛從原始服務器獲取不久;其餘的值則是表示代理服務器當前的系統時間與此應答消息中的通用消息頭 Date的值之差。
上面這個結論歸結爲一個等式就是:
靜態資源Age + 靜態資源Date = 原服務端Date
搜索了好久有沒有關於這方面的權威總結,最後居然在百度百科找到了也是很驚訝,我本身加了一條用戶強制刷新操做瀏覽器的反應。強制刷新,window下是Ctrl+F5
,mac下就是command+shift+R
操做了。:relieved:
操做 | 說明 |
---|---|
打開新窗口 | 若是指定cache-control的值爲private、no-cache、must-revalidate,那麼打開新窗口訪問時都會從新訪問服務器。而若是指定了max-age值,那麼在此值內的時間裏就不會從新訪問服務器,例如:Cache-control: max-age=5 表示當訪問此網頁後的5秒內不會去再次訪問服務器. |
在地址欄回車 | 若是值爲private或must-revalidate,則只有第一次訪問時會訪問服務器,之後就再也不訪問。若是值爲no-cache,那麼每次都會訪問。若是值爲max-age,則在過時以前不會重複訪問。 |
按後退按扭 | 若是值爲private、must-revalidate、max-age,則不會重訪問,而若是爲no-cache,則每次都重複訪問. |
按刷新按扭 | 不管爲什麼值,都會重複訪問.(可能返回狀態碼:200、304,這個不一樣瀏覽器處理是不同的,FireFox正常,Chrome則會啓用緩存(200 from cache)) |
按強制刷新按鈕 | 當作首次進入從新請求(返回狀態碼200) |
:wink:若是想在瀏覽器點擊「刷新」按鈕的時候不讓瀏覽器去發新的驗證請求呢?辦法找到一個,知乎上面一個回答,在頁面加載完畢後經過腳本動態地添加資源:
$(window).load(function() { var bg='http://img.infinitynewtab.com/wallpaper/100.jpg'; setTimeout(function() { $('#bgOut').css('background-image', 'url('+bg+')'); },0); });
這部分準備的說應該叫離線存儲。如今比較廣泛用的是Appcache
,但Appcache
已經從web標準移除了,在可預見的將來裏,ServiceWorker
可能會是一個比較適合的解決方案。
這是HTML5的一個新特性,經過離線存儲達到用戶在沒有網絡鏈接的狀況下也能訪問頁面的功能。離線狀態下即便用戶點擊刷新都能正常加載文檔。
使用方法以下,在HTML文件中引入appcache
文件:
<!DOCTYPE html> <html manifest="manifest.appcache"> <head> <meta charset="UTF-8"> <title>***</title> </head> <body> <div id="root"></div> </body> </html>
🤠web 應用中的 manifest
特性能夠指定爲緩存清單文件的相對路徑或一個絕對 URL(絕對 URL 必須與應用同源)。緩存清單文件可使用任意擴展名,但傳輸它的 MIME 類型必須爲 text/cache-manifest。
**注意:**在 Apache 服務器上,若要設置適用於清單(.appcache)文件的 MIME 類型,能夠向根目錄或應用的同級目錄下的一個 .htaccess 文件中增長
AddType text/cache-manifest .appcache
。
CACHE MANIFEST
# 註釋:須要緩存的文件,不管在線與否,均從緩存裏讀取 # v1 2017-11-30 # This is another comment /static/logo.png # 註釋:不緩存的文件,始終從網絡獲取 NETWORK: example.js # 註釋:獲取不到資源時的備選路徑,如index.html訪問失敗,則返回404頁面 FALLBACK: index.html 404.html
上面就是一個完整的緩存清單文件的示例。
**注意:**主頁必定會被緩存起來的,由於AppCache主要是用來作離線應用的,若是主頁不緩存就沒法離線查看了,所以把index.html添加到NETWORK中是不起效果的。
實際上這個特性已經web標準中刪除,但如今爲止還有不少瀏覽器支持它,因此這裏提一下。
你能夠用最新的Firefox(版本 57.0.1)測試下,控制檯會有這麼一行字👉:
程序緩存 API(AppCache)已不同意使用,幾天後將被移除。需離線支持請嘗試使用 Service Worker。
最新Chrome(版本 62.0.3202.94)卻是沒有這個警告。🐻
AppCache
之因此不受待見我想了下面幾個緣由:
Service worker仍是一個實驗性的功能,線上環境不推薦使用。🐒這裏大概介紹一下。
Service worker本質上充當Web應用程序與瀏覽器之間的代理服務器。
🙂首先講個小故事:
咱們都知道瀏覽器的js引擎處理js是單線程的,它就好像一個大Boss高高在上,同一個時間它只作一個事情(就是那麼傲嬌),基於這個弊端,W3C(HR)給大Boss招聘了一個祕書(web worker
),大Boss能夠把瑣碎的事情交給祕書web worker
去作,作完了發個微信(postMessage
)通知大Boss,大Boss經過onmessage
來獲取祕書web worker
作的事情的結果。傍晚時分,下班時間到!大Boss回家哄兒子了,祕書也出去約會去了,沒人加班了!這怎麼行!W3C(HR)又提出了招個程序🐵的想法的想法,OK,Service Worker
應聘成功!因而,程序🙈就堅持在工做崗位上了,今後開啓沒完沒了的加班之路。總的來講這隻猿的工做是這樣的:
注意:Service workers之因此優於之前同類嘗試(如上面提到的AppCache)),是由於它們沒法支持當操做出錯時終止操做。Service workers能夠更細緻地控制每一件事情。如何控制的呢?
Service workers利用了ES6中比較重要的特性Promise,而且在攔截請求的時候使用的是新的fetch API,之因此使用fetch就是由於fetch返回的是Promise對象。能夠說Service workers重要組成部分就是三塊:事件、Promise和Fetch請求。OK,talk is cheap,show you the code。🤓
首先咱們看下app.js文件:告訴瀏覽器註冊某個JavaScript文件爲service worker,檢查service worker API是否可用,若是可用就註冊service worker:
//使用 ServiceWorkerContainer.register()方法首次註冊service worker。 if (navigator.serviceWorker) { navigator.serviceWorker.register('./sw.js', {scope: './'}) .then(function (registration) { console.log(registration); }) .catch(function (e) { console.error(e); }); } else { console.log('該瀏覽器不支持Service Worker'); }
再來看看具體做爲service worker的文件sw.js,例子以下:
const CACHE_VERSION = 'v1'; // 緩存文件的版本 const CACHE_FILES = [ // 須要緩存的文件 './test.js', './app.js', 'https://code.jquery.com/jquery-3.0.0.min.js' ]; self.addEventListener('install', function (event) { // 監聽worker的install事件 event.waitUntil( // 延遲install事件直到緩存初始化完成 caches.open(CACHE_VERSION) .then(function (cache) { console.log('緩存打開'); return cache.addAll(CACHE_FILES); }) ); }); self.addEventListener('activate', function(event) {// 監聽worker的activate事件 event.waitUntil(// 延遲activate事件直到 caches.keys().then(function(keys) { return Promise.all(keys.map(function(key, i){ if(key !== CACHE_VERSION){ return caches.delete(keys[i]); // 清除舊版本緩存 } })) }) ) }); self.addEventListener('fetch', function(event) { // 截取頁面的資源請求 event.respondWith( caches.match(event.request).then(function(res) { // 判斷緩存是否命中 if (res) { // 返回緩存中的資源 return res; } _request(event); // 執行請求備份操做 }) ) }); function _request(event) { var url = event.request.clone(); return fetch(url).then(function(res) {// 使用fetch請求線上資源 // 錯誤判斷 if (!res || res.status !== 200 || res.type !== 'basic') { return res; } var response = res.clone(); // 建立了一個響應對象的克隆,儲藏在一個單獨的變量中 caches.open(CACHE_VERSION).then(function(cache) {// 緩存從線上獲取的資源 cache.put(event.request, response); }); return res; }) }
清除一個Service Worker也很簡單:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js', {scope: './'}).then(function(registration) { // registration worked console.log('Registration succeeded.'); registration.unregister().then(function(boolean) { // if boolean = true, unregister is successful }); }).catch(function(error) { // registration failed console.log('Registration failed with ' + error); }); };
相對AppCache來講,Service Worker的API增多了很多,用法也更復雜了些,但看得出Service Worker纔是將來,對於web app來講,更是如虎添翼。如今支持Service Worker的瀏覽器除了Chrome和Firefox,最近新添一個生力軍——Safari也支持Service Worker了。期待它在將來大放異彩吧。🤗
以下,使用node原生代碼簡單的模擬下服務器發送響應的過程,包括對於協商緩存的處理過程:
var http = require('http'); var fs = require('fs'); var url = require('url'); process.env.TZ = 'Europe/London'; let tag = '123456'; http.createServer( function (request, response) { var pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); const fileMap = { 'js': 'application/javascript; charset=utf-8', 'html': 'text/html', 'png': 'image/png', 'jpg': 'image/jpeg', 'gif': 'image/gif', 'ico': 'image/*', 'appcache': 'text/cache-manifest' } fs.readFile(pathname.substr(1), function (err, data) { if (request.headers['if-none-match'] === tag) { response.writeHead(304, { 'Content-Type': fileMap[pathname.substr(1).split('.')[1]], 'Expires': new Date(Date.now() + 30000), 'Cache-Control': 'max-age=10, public', 'ETag': tag, 'Last-Modified': new Date(Date.now() - 30000), 'Vary': 'User-Agent' }); } else { response.writeHead(200, { 'Content-Type': fileMap[pathname.substr(1).split('.')[1]], 'Cache-Control': 'max-age=10, public', 'Expires': new Date(Date.now() + 30000), 'ETag': tag, 'Last-Modified': new Date(Date.now() - 30000), 'Vary': 'User-Agent' }); response.write(fs.readFileSync(pathname.substr(1))); } response.end(); }); }).listen(8081);
如上代碼。若是你沒使用過node,拷貝下代碼存爲file.js,安裝node,命令行輸入node file.js
,能夠在同目錄下創建index.html文件,在html文件中引用一些圖片,CSS等文件,瀏覽器輸入localhost:8081/index.html
進行模擬。🤓
1. 問題:請求被緩存,致使新代碼未生效
解決方案:
Cache-Control:no-cache,must-revalidate
指令;If-modified-since:0
或If-none-match
;2. 問題:服務端緩存致使本地代碼未更新
解決方案:
3. 問題: Cache-Control: max-age=0 和 no-cache有什麼不一樣
回答:
max-age=0
和no-cache
應該是從語氣上不一樣。max-age=0
是告訴客戶端資源的緩存到期應該向服務器驗證緩存的有效性。而no-cache
則告訴客戶端使用緩存前必須向服務器驗證緩存的有效性。