閱讀目錄javascript
一:什麼是離線優先?php
傳統的web應用徹底依賴於服務器端,好比像很早之前jsp,php,asp時代,全部的數據,內容和應用邏輯都在服務器端,客戶端僅僅作一些html內容渲染到頁面上去。可是隨着技術在不斷的改變,如今不少業務邏輯也放在前端,先後端分離,前端是作模板渲染工做,後端只作業務邏輯開發,只提供數據接口。可是咱們的web前端開發在數據層這方面來說仍是依賴於服務器端。若是網絡中斷或服務器接口掛掉了,都會影響數據頁面展現。所以咱們須要使用離線優先這個技術來更優雅的處理這個問題。css
擁抱離線優先的真正含義是:儘管應用程序的某些功能在用戶離線時可能不能正常使用,可是更多的功能應該保持可用狀態。html
離線優先它能夠優雅的處理這些異常狀況下問題,當用戶離線時,用戶正在查看數據多是以前的數據,可是仍然能夠訪問以前的頁面,以前的數據不會丟失,這就意味着用戶能夠放心使用某些功能。那麼要作到離線時候還能夠訪問,就須要咱們緩存哦。前端
二:經常使用的緩存模式java
在爲咱們的網站使用緩存以前,咱們須要先熟悉一些常見的緩存設計模式。若是咱們要作一個股票K線圖的話,由於股票數據是實時更新的,所以咱們須要實時的去請求網絡最新的數據(固然實時確定使用websocket技術,而不是http請求,我這邊是假如)。只有當網絡請求失敗的時候,咱們再從緩存裏面去讀取數據。可是對於股票K線圖中的一些圖標展現這樣的,由於這些圖標是通常不會變的,因此咱們更傾向於使用緩存裏面的數據。只有在緩存裏面找不到的狀況下,再從網絡上請求數據。node
因此有以下幾種緩存模式:webpack
1. 僅緩存
2. 緩存優先,網絡做爲回退方案。
3. 僅網絡。
4. 網絡優先,緩存做爲回退方案。
5. 網絡優先,緩存做爲回退方案, 通用回退。git
1. 僅緩存github
什麼是僅緩存呢?僅緩存是指 從緩存中響應全部的資源請求,若是緩存中找不到的話,那麼請求就會失敗。那麼僅緩存對於靜態資源是實用的。由於靜態資源通常是不會發生變化,好比圖標,或css樣式等這些,固然若是css樣式發生改變的話,在後綴能夠加上時間戳這樣的。好比 base.css?t=20191011 這樣的,若是時間戳沒有發生改變的話,那麼咱們直接從緩存裏面讀取。
所以咱們的 sw.js 代碼能夠寫成以下(注意:該篇文章是在上篇文章基礎之上的,若是想看上篇文章,請點擊這裏:
self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request) ) });
如上代碼,直接監聽 fetch事件,該事件能監聽到頁面上全部的請求,當有請求過來的時候,它使用緩存裏面的數據依次去匹配當前的請求,若是匹配到了,就拿緩存裏面的數據,若是沒有匹配到,則請求失敗。
2. 緩存優先,網絡做爲回退方案
該模式是:先從緩存裏面讀取數據,當緩存裏面沒有匹配到數據的時候,service worker纔會去請求網絡並返回。
代碼變成以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ) });
如上代碼,使用fetch去監聽全部請求,而後先使用緩存依次去匹配請求,不論是匹配成功仍是匹配失敗都會進入then回調函數,當匹配失敗的時候,咱們的response值就爲 undefined,若是爲undefined的話,那麼就網絡請求,不然的話,從拿緩存裏面的數據。
3. 僅網絡
傳統的web模式,就是這種模式,從網絡裏面去請求,若是網絡不通,則請求失敗。所以代碼變成以下:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request) ) });
4. 網絡優先,緩存做爲回退方案。
先從網絡發起請求,若是網絡請求失敗的話,再從緩存裏面去匹配數據,若是緩存裏面也沒有找到的話,那麼請求就會失敗。
所以代碼以下:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request).catch(function() { return caches.match(event.request); }) ) });
5. 網絡優先,緩存做爲回退方案, 通用回退
該模式是先請求網絡,若是網絡失敗的話,則從緩存裏面讀取,若是緩存裏面讀取失敗的話,咱們提供一個默認的顯示給頁面展現。
好比顯示一張圖片。以下代碼:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request).catch(function() { return caches.match(event.request).then(function(response) { return response || caches.match("/xxxx.png"); }) }); ) });
三:混合與匹配,創造新模式
上面是咱們五種緩存模式。下面咱們須要將這些模式要組合起來使用。
1. 緩存優先,網絡做爲回退方案, 並更新緩存。
對於不常常改變的資源,咱們能夠先緩存優先,網絡做爲回退方案,第一次請求完成後,咱們把請求的數據緩存起來,下次再次執行的時候,咱們先從緩存裏面讀取。
所以代碼以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return cache.match(event.request).then(function(cachedResponse){ return cachedResponse || fetch(event.request).then(function(networkResponse){ cache.put(event.request, networkResponse.clone()); return networkResponse; }); }) }) ) });
如上代碼,咱們首先打開緩存,而後使用請求匹配緩存,無論匹配成功了仍是匹配失敗了,都會進入then回調函數,若是匹配到了,說明緩存裏面有對應的數據,那麼直接從緩存裏面返回,若是緩存裏面 cachedResponse 值爲undefined,沒有的話,那麼就從新使用fetch請求網絡,而後把請求的數據 networkResponse 從新返回回來,而且克隆一份 networkResponse 放入緩存裏面去。
2. 網絡優先,緩存做爲回退方案,並頻繁更新緩存
若是一些常常要實時更新的數據的話,好比百度上的一些實時新聞,那麼都須要對網絡優先,緩存做爲回退方案來作,那麼該模式下首先會從網絡中獲取最新版本,當網絡請求失敗的時候纔回退到緩存版本,當網絡請求成功的時候,它會將當前返回最新的內容從新賦值給緩存裏面去。這樣就保證緩存永遠是上一次請求成功的數據。即便網絡斷開了,仍是會使用以前最新的數據的。
所以代碼能夠變成以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return fetch(event.request).then(function(networkResponse) { cache.put(event.request, networkResponse.clone()); return networkResponse; }).catch(function() { return caches.match(event.request); }); }) ) });
如上代碼,咱們使用fetch事件監聽全部的請求,而後打開緩存後,咱們先請網絡請求,請求成功後,返回最新的內容,此時此刻同時把該返回的內容克隆一份放入緩存裏面去。可是當網絡異常的狀況下,咱們就匹配緩存裏面最新的數據。可是在這種狀況下,若是咱們第一次網絡請求失敗後,因爲第一次咱們沒有作緩存,所以緩存也會失敗,最後就會顯示失敗的頁面了。
3. 緩存優先,網絡做爲回退方案,並頻繁更新緩存
對於一些常常改變的資源文件,咱們能夠先緩存優先,而後再網絡做爲回退方案,也就是說先緩存裏面找到,也總會從網絡上請求資源,這種模式能夠先使用緩存快速響應頁面,同時會從新請求來獲取最新的內容來更新緩存,在咱們用戶下次請求該資源的時候,那麼它就會拿到緩存裏面最新的數據了,這種模式是將快速響應和最新的響應模式相結合。
所以咱們的代碼改爲以下:
self.addEventListener('fetch', function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return cache.match(event.request).then(function(cachedResponse) { var fetchPromise = fetch(event.request).then(function(networkResponse) { cache.put(event.request, networkResponse.clone()); return networkResponse; }); return cachedResponse || fetchPromise; }); }) ) });
如上代碼,咱們首先打開一個緩存,而後咱們試圖匹配請求,不論是否匹配成功,咱們都會進入then函數,在該回調函數內部,會先從新請求一下,請求成功後,把最新的內容返回回來,而且以此同時把該請求數的數據克隆一份出來放入緩存裏面去。最後把請求的資源文件返回保存到 fetchPromise 該變量裏面,最後咱們先返回緩存裏面的數據,若是緩存裏面沒有數據,咱們再返回網絡fetchPromise 返回的數據。
如上就是咱們3種常見的模式。下面咱們就須要來規劃咱們的緩存策略了。
四:規劃緩存策略
在咱們以前講解的demo中(https://www.cnblogs.com/tugenhua0707/p/11148968.html), 都是基於網絡優先,緩存做爲回退方案模式的。咱們以前使用這個模式給用戶體驗仍是挺不錯的,首先先請求網絡,當網絡斷開的時候,咱們從緩存裏面拿到數據。
這樣就不會使頁面異常或空白。可是上面咱們已經瞭解到了緩存了,咱們能夠再進一步優化了。
咱們如今可使用離線優先的方式來構建咱們的應用程序了,對應咱們項目常常會改變的資源咱們優先使用網絡請求,若是網絡不能夠用的話,咱們使用緩存裏面的數據。
首先仍是看下咱們項目的整個目錄結構以下:
|----- 項目 | |--- public | | |--- js # 存放全部的js | | | |--- main.js # js入口文件 | | |--- style # 存放全部的css | | | |--- main.styl # css 入口文件 | | |--- index.html # index.html 頁面 | | |--- images | |--- package.json | |--- webpack.config.js | |--- node_modules | |--- sw.js
咱們的首頁 index.html 代碼以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 實列</title> <link rel="stylesheet" href="/main.css" /> </head> <body> <div id="app">22222</div> <img src="/public/images/xxx.jpg" /> <script type="text/javascript" src="/main.js"></script> </body> </html>
首頁是由靜態的index.html 組成的,它通常不多會隨着版本的改變而改變的,它頁面中會請求多個圖片,請求多個css樣式,和請求多個js文件。在index.html中全部的靜態資源文件(圖片、css、js)等在咱們的service worker安裝過程當中會緩存下來的,那麼這些資源文件適合的是 "緩存優先,網絡做爲回退方案" 模式來作。這樣的話,頁面加載會更快。
可是index.html呢?這個頁面通常狀況下不多改變,咱們通常會想到 "緩存優先,網絡做爲回退方案" 來考慮,可是若是該頁面也改動了代碼呢?咱們若是一直使用緩存的話,那麼咱們就得不到最新的代碼了,若是咱們想咱們的index.html拿到最新的數據,咱們不得不從新更新咱們的service worker,來獲取最新的緩存文件。可是咱們從以前的知識點咱們知道,在咱們舊的service worker 釋放頁面的同時,新的service worker被激活以前,頁面也不是最新的版本的。必需要等第二次從新刷新頁面的時候纔會看到最新的頁面。那麼咱們的index.html頁面要如何作呢?
1) 若是咱們使用 "緩存優先,網絡做爲回退方案" 模式來提供服務的話,那麼這樣作的話,當咱們改變頁面的時候,它就有可能不會使用最新版本的頁面。
2)若是咱們使用 "網絡優先,緩存做爲回退方案 " 模式來作的話,這樣確實能夠經過請求來顯示最新的頁面,可是這樣作也有缺點,好比咱們的index.html頁面沒有改過任何東西的話,也要從網絡上請求,而不是從緩存裏面讀取,致使加載的時間會慢一點。
3) 使用 緩存優先,網絡做爲 回退方案,並頻繁更新緩存模式。該模式老是從緩存裏面讀取 index.html頁面,那麼它的響應時間相對來講是很是快的,而且從緩存裏面讀取頁面後,咱們同時會請求下,而後返回最新的數據,咱們把最新的數據來更新緩存,所以咱們下一次進來頁面的時候,會使用最新的數據。
所以對於咱們的index.html頁面,咱們適合使用第三種方案來作。
所以對於咱們這個簡單的項目來說,咱們能夠總結以下:
1. 使用 "緩存優先,網絡做爲回退方案,並頻繁更新緩存" 模式來返回index.html文件。
2. 使用 "緩存優先,網絡做爲回退方案" 來返回首頁須要的全部靜態文件。
所以咱們可使用上面兩點,來實現咱們的緩存策略。
五:實現緩存策略
如今咱們來更新下咱們的 sw.js 文件,該文件來緩存咱們index.html,及在index.html使用到的全部靜態資源文件。
index.html 代碼改爲以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 實列</title> </head> <body> <div id="app">22222</div> <img src="/public/images/xxx.jpg" /> </body> </html>
js/main.js 代碼變爲以下:
// 加載css樣式 require('../styles/main.styl'); if ("serviceWorker" in navigator) { navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) { console.log("Service Worker registered with scope: ", registration.scope); }).catch(function(err) { console.log("Service Worker registered failed:", err); }); }
sw.js 代碼變成以下:
var CACHE_NAME = "cacheName"; var CACHE_URLS = [ "/public/index.html", // html文件 "/main.css", // css 樣式表 "/public/images/xxx.jpg", // 圖片 "/main.js" // js 文件 ]; // 監聽 install 事件,把全部的資源文件緩存起來 self.addEventListener("install", function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(CACHE_URLS); }) ) }); // 監聽fetch事件,監聽全部的請求 self.addEventListener("fetch", function(event) { var requestURL = new URL(event.request.url); console.log(requestURL); if (requestURL.pathname === '/' || requestURL.pathname === "/index.html") { event.respondWith( caches.open(CACHE_NAME).then(function(cache) { return cache.match("/index.html").then(function(cachedResponse) { var fetchPromise = fetch("/index.html").then(function(networkResponse) { cache.put("/index.html", networkResponse.clone()); return networkResponse; }); return cachedResponse || fetchPromise; }) }) ) } else if (CACHE_URLS.includes(requestURL.href) || CACHE_URLS.includes(requestURL.pathname)) { event.respondWith( caches.open(CACHE_NAME).then(function(cache) { return cache.match(event.request).then(function(response) { return response || fetch(event.request); }); }) ) } }); self.addEventListener("activate", function(e) { e.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (CACHE_NAME !== cacheName && cacheName.startWith("cacheName")) { return caches.delete(cacheName); } }) ) }) ) });
如上代碼中的fetch事件,var requestURL = new URL(event.request.url);console.log(requestURL); 打印信息以下所示:
如上咱們使用了 new URL(event.request.url) 來決定如何處理不一樣的請求。且能夠獲取到不一樣的屬性,好比host, hostname, href, origin 等這樣的信息到。
如上咱們監聽 fetch 事件中全部的請求,判斷 requestURL.pathname 是不是 "/" 或 "/index.html", 若是是index.html 頁面的話,對於 index.html 的來講,使用上面的原則是:使用 "緩存優先,網絡做爲回退方案,並頻繁更新緩存", 因此如上代碼,咱們首先打開咱們的緩存,而後使用緩存匹配 "/index.html",無論匹配是否成功,都會進入then回調函數,而後把緩存返回,在該函數內部,咱們會從新請求,把請求最新的內容保存到緩存裏面去,也就是說更新咱們的緩存。當咱們第二次訪問的時候,使用的是最新緩存的內容。
若是咱們請求的資源文件不是 index.html 的話,咱們接着會判斷下,CACHE_URLS 中是否包含了該資源文件,若是包含的話,咱們就從緩存裏面去匹配,若是緩存沒有匹配到的話,咱們會從新請求網絡,也就是說咱們對於頁面上全部靜態資源文件話,使用 "緩存優先,網絡做爲回退方案" 來返回首頁須要的全部靜態文件。
所以咱們如今再來訪問咱們的頁面的話,以下所示:
如上所示,咱們能夠看到,咱們第一次請求的時候,加載index.html 及 其餘的資源文件,咱們能夠從上圖能夠看到 加載時間的毫秒數,雖然從緩存裏面讀取第一次數據後,可是因爲咱們的index.html 老是會請求下,把最新的資源再返回回來,而後更新緩存,所以咱們能夠看到咱們第二次加載index.html 及 全部的service worker中的資源文件,能夠看到第二次的加載時間更快,而且當咱們修改咱們的index.html 後,咱們刷新下頁面後,第一次仍是從緩存裏面讀取最新的數據,當咱們第二次刷新的時候,頁面纔會顯示咱們剛剛修改的index.html頁面的最新頁面了。所以就驗證了咱們以前對於index.html 處理的邏輯。
使用 緩存優先,網絡做爲 回退方案,並頻繁更新緩存模式。該模式老是從緩存裏面讀取 index.html頁面,那麼它的響應時間相對來講是很是快的,而且從緩存裏面讀取頁面後,咱們同時會請求下,而後返回最新的數據,咱們把最新的數據來更新緩存,所以咱們下一次進來頁面的時候,會使用最新的數據。
github簡單的demo