合理的使用緩存能夠極大地提升網站的性能優點,還能夠節約帶寬從而下降服務器成本。可是不少站點有隻弄對了一半或者一半都沒有,若是是這樣,就徹底沒有發揮出緩存的優點。很大程度上產生會因爲靜態資源的競爭關係而致使依賴的靜態資源不一樣步。javascript
如下爲兩個最佳靜態資源緩存實踐的例子。css
// 設置緩存時間爲1年 Cache-Control: max-age=31536000
瀏覽器請求了/index-v1.js
、/base-v1.css
以及/dog-v1.png
這三個資源。html
此次瀏覽器請求了/index-v2.js
、/base-v2.css
以及/dog-v1.png
這三個資源。前端
此處注意:index.js
和base.css
與第一天請求的版本號不一樣。
在一年的時間裏,瀏覽器再也沒有請求過/index-v1.js
、/base-v1.css
以及/dog-v1.png
這三個資源,瀏覽器緩存就會把它們給刪掉。java
因此在這個例子中,爲了讓緩存發揮最大效率,你要作的並非更改文件的內容,而是應該更改資源的URL:node
<script src="/index-v3.js"></script> <link rel="stylesheet" href="/base-v3.css"> <img src="/dog-v3.jpg" alt="…">
每個靜態資源URL都應該跟隨其內容的修改而改變。例如示例index-v1.js
中的v1
,你對它的命名不須要有任何限制。它能夠是一個版本號
,最後修改的日期
,或者根據內容計算出來的散列值
。webpack
絕大多數服務器端的框架都提供了工具來實現這一點,一樣的在nodejs中有不少優秀的庫來實現這個功能,好比gulp-rev、webpack、fis3。git
Cache-Control: no-cache
注意:
no-cache
並不意味着不緩存。它的意思是在使用緩存資源以前,它必須通過服務器的檢查(revalidate
也能夠實現這個功能)。
no-store
纔是告訴瀏覽器不要緩存它。此外,must-revalidate
並不意味着必須從新認證
,它的前提是資源還在max-age
的緩存期內,不然必須從新認證。
在此模式下 ,你也能夠將ETag(你選擇的版本ID)
或者Last-modified
日期添加到響應首部中。客戶端下次獲取資源時,他會分別經過If-None-Match
(與ETage對應)和If-Modified-Since
(與Last-Mofied對應)兩個請求首部將值發送給服務器。若是服務器發現兩次值都是對等的,就是返回一個HTTP 304
。github
若是沒有發送ETag
和Last-Modified
,那麼服務器將始終返回完整的資源內容。web
可是這種方法有個缺點,就是它每次都會去服務器作一次驗證,涉及到了網絡提取,因此它不如第一個例子那樣能夠徹底繞過網絡。
這種狀況並很多見,例如它就實實在在地發生在了github的頁面上。
想象一下 :
它們所有使用的是:
// 十分鐘內不須要從新認證,超過十分鐘就須要從新認證 Cache-Control: must-revalidate, max-age=600
If-Modified-Since
和If-None-Match
從新進行服務器認證。第一次請求:
六七分鐘事後:
最終:
這種狀況在測試中常常出現。可是想象一下,在線上環境你永遠不知道瀏覽器前面坐着的是什麼樣的人,他頗有可能無心中胡亂地用鼠標點點點,就打亂了瀏覽器的靜態資源緩存機制,致使頁面發生了錯亂,並且真的很難追蹤。
在上面的例子中,服務器實際上已經更新了HTML、CSS和JS,可是頁面最後使用的是緩存中舊的HTML和JS,以及剛從服務器下載的最新的CSS。多個靜態資源版本之間不匹配的問題隨之出現。
一般,當咱們對HTML進行重大修改時,咱們可能會更改CSS文件來適配新的DOM結構,而且更新JS來配置樣式和DOM的修改。這些資源都是相互依賴的,但攜帶緩存信息的HTTP首部
可無論你這些有的沒的。最終,用戶頗有可能會獲得一個/兩個靜態資源新版本,而其餘資源都是舊版本。
max-age是相對於服務器響應時間的,因此若是全部上述資源都在同一時間請求,即使它們都被設置爲了相同的max-age時長,它們仍然存在很小的競爭可能性(畢竟有的資源先返回有的資源後返回)。若是你的某些頁面不包含JS,或者包含不一樣的CSS,它們的緩存失效時間就有可能會不一樣步。更噁心的是,瀏覽器始終會從緩存中刪除和獲取資源,它並不知道這些資源中哪一個是相互依賴的,只要過了緩存時間它就會絕不猶豫地刪掉一個,並不會刪掉這個過時文件所依賴的其餘資源。把上面的種種可能性加在一塊兒,就會大機率出現靜態資源版本不匹配的問題。
不過還好,咱們還有法子來解決這個問題:
在強制刷新瀏覽器或者清除緩存後,請求的頁面以及頁面內的全部資源會忽略以前的max-age
,去服務器作從新認證。所以,若是用戶因爲max-age出現問題以後,只須要強制刷新或者清緩存就能夠修復問題。固然,強迫用戶這樣作只會讓它們下降對你網站的信任度,認爲你的網站不靠譜。。。
service Worker的執行時機:
註冊serviceWorker:
if (navigator.serviceWorker) { navigator.serviceWorker.register('/serviceworker.js', { scope: '/' }); }
執行serviceworker.js:
const version = '2'; self.addEventListener('install', event => { // 因爲系統會隨時睡眠SW,因此,爲了防止執行中斷,就須要使用 event.waitUntil 進行捕獲 event.waitUntil( caches.open(`static-${version}`) .then(cache => cache.addAll( // 不穩定文件或者大文件加載 //... ), cache.addAll([ // 穩定文件或小文件加載 '/styles.css', '/script.js' ])); ); }); self.addEventListener('activate', event => { // …delete old caches… }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });
若是咱們修改了JS/CSS,只需修改version
就可讓service worker觸發更新。
你也能夠在service worker中跳過緩存:
self.addEventListener('install', event => { event.waitUntil( caches.open(`static-${version}`) .then(cache => cache.addAll([ new Request('/styles.css', { cache: 'no-cache' }), new Request('/script.js', { cache: 'no-cache' }) ])) ); });
不過很不巧的是,cache
選項在和safari和opera中都不支持 ,只有firefox和chrome最近纔開始支持。可是你能夠這樣作:
self.addEventListener('install', event => { event.waitUntil( caches.open(`static-${version}`) .then(cache => Promise.all( [ '/styles.css', '/script.js' ].map(url => { // cache-bust using a random query string return fetch(`${url}?${Math.random()}`).then(response => { // fail on 404, 500 etc if (!response.ok) throw Error('Not ok'); return cache.put(url, response); }) }) )) ); });
你可使用上面代碼中的隨機字符串,也可使用散列值。這有點像在javascript中實現文章剛開始第一小節的方法,不過僅僅是在server worker中使用。
經過上個的例子,你能夠看到service worker能夠很好的處理一些糟糕的緩存狀況。可是僅僅是作一些hack處理而已,最重要的是再根源上解決問題。正確的使用緩存不只能夠更好地使用service worker,還能夠很好地在那些不支持service worker的瀏覽器(IE/Safari/Opera)上提升網站的性能。除此以外,對你的CDN也是大有益處。
正確的使用緩存,能夠大量簡化service worker的代碼:
const version = '23'; self.addEventListener('install', event => { event.waitUntil( caches.open(`static-${version}`) .then(cache => cache.addAll([ '/', '/script-v3.js', '/styles-v3.css', '/dog-v3.jpg' ])) ); });
因此,咱們可使用第二小節的方法(服務器從新認證)來緩存根HTML頁面。並使用第一小節的方法(不一樣的內容使用不一樣的URL)來緩存其餘資源。每次service worker更新世都會去請求網站的根HTML頁面,其餘資源只有在更改URL時纔會去下載,從而提升網站的性能。
雖然service worker擅長提升網站的性能,但它並非一個完整的解決方案。所以要和HTTP cache配合使用才能夠顯著地提升性能。
在內容常常修改可是URL不變的靜態資源上使用max-age
在一般意義上來講不是一個好點子,但事實卻不老是如此。
假如一個頁面的max-age
爲三分鐘,而且在這個頁面上不須要考慮靜態資源的競爭關係(靜態資源之間存在相互依賴,見第三小節),因此在這個頁面上不存在任何的靜態資源依賴。在這種狀況下就能夠盡情使用max-age
。不過這也意味着網站的修改要再三分鐘以後才能夠被看到。
不過要是頁面存在靜態資源競爭關係的話,這種法子很差用了,好比我如今有兩個文章A和B,我如今文章A中添加一個新的章節,而後在文章B中增長了一個指向文章A新增章節的超連接。而後我從文章B中訪問這個連接,假如文章A的max-age沒有過時,那麼我訪問到的文章A裏將會發現文章並無那個新增的章節。此時只能等max-age
過時或者強制刷新瀏覽器,再或者清除緩存了。因此,必定要謹慎使用這種方法。
正確使用緩存能夠代理巨大的性能收益而且有效節省服務器帶寬。既支持版本號類型的靜態資源緩存方式也支持服務器從新認證(no-cache、304)的方式。若是你以爲本身很勇敢,那麼大可混合使用max-age和『內容常常修改可是URL不變的靜態資源』,可是前提你得肯定本身的HTML中沒有靜態資源競爭關係。