因爲項目中有個問題涉及到了Service Worker,因此找了時間去研究了一下PWA。也趁此寫一篇文章總結一下。javascript
前言:PWA做爲今年最火熱的技術概念之一,對提高Web應用的安全、性能和體驗有着很大的意義,很是值得咱們去了解與學習。css
PWA,全稱Progressive Web App,即漸進式WEB應用, 是提高 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。它的優點主要體如今:html
PWA自己實際上是一個概念集合,它不是指某一項技術,而是經過一系列的Web技術與Web標準來優化Web App的安全、性能和體驗。其中涉及到的一些技術概念包括但不限於:java
本文主要講一下Service Worker相關的東西。git
Service worker是一個註冊在指定源和路徑下的事件驅動worker。它採用JavaScript控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地緩存資源。你能夠徹底控制應用在特定情形(最多見的情形是網絡不可用)下的表現。es6
Service worker運行在worker上下文,所以它不能訪問DOM。相對於驅動應用的主JavaScript線程,它運行在其餘線程中,因此不會形成阻塞。它設計爲徹底異步,同步API(如XHR和localStorage)不能在service worker中使用。chrome
下圖展現普通Web App與添加了Service Worker的Web App在網絡請求上的差別:數組
在index.js文件裏面註冊Service Worker。promise
// index.js
// 註冊service worker,service worker腳本文件爲sw.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(function () {
console.log('Service Worker 註冊成功');
});
}
複製代碼
值得一提的是,Service Worker裏的各種操做都被設計爲異步,以免一些長時間的阻塞操做。這些異步操做都是創建在Promise的基礎上的,若是你對Promise不夠了解,建議去熟悉一下Promise。傳送門:Promise(ES6標準入門)瀏覽器
當咱們註冊了Service Worker後,它會經歷生命週期的各個階段,同時會觸發相應的事件。整個生命週期包括了:installing --> installed --> activating --> activated --> redundant。當Service Worker安裝(installed)完畢後,會觸發install事件;而激活(activated)後,則會觸發activate事件。
下面的例子監聽了install事件
// 在sw.js裏面
// 監聽install事件
self.addEventListener('install', function (e) {
console.log('Service Worker installed');
});
複製代碼
self
是Service Worker中的一個特殊的全局變量,相似於window
。self
指向當前這個Service Worker。
通常狀況下,咱們會列出一個須要緩存的資源列表,當Service Worker install時,會將改列表的資源緩存下來。
// sw.js
var cacheName = 'v1';
var cacheFiles = [
'/',
'./index.html',
'./index.js',
'./index.css'
];
// 監聽install事件,安裝完成後,進行文件緩存
self.addEventListener('install', e => {
console.log(e);
e.waitUntil(
caches.open(cacheStorageKey)
.then(cache => cache.addAll(cacheList))
.then(_ => self.skipWaiting()) // 該函數可以使新的sw.js立刻生效。
);
})
複製代碼
看完這段代碼,你可能會有所疑惑。caches
是個什麼鬼東西?
caches
是暴露在window做用域的一個變量,咱們經過caches
屬性訪問CacheStorage
。
CacheStorage
是一種新的本地存儲,它的存儲結構是這樣的:
每一個域有若干個存儲模塊,每一個模塊內能夠存儲若干個鍵值對。 它的鍵是網絡請求(Request),值是請求對應的響應(Response)。 CacheStorage
的接口集中在全局變量caches
中,且僅在HTTPS協議(或localhost:*域)下可用。
咱們在chrome上的devtool-application中能夠看到CacheStorage
的相關信息。
介紹變量caches
經常使用方法
open(cacheName)
返回一個 Promise
,resolve爲匹配 cacheName
(若是不存在則建立一個新的cache)的 Cache
對象。
keys()
返回一個 Promise
,它將使用一個包含與 CacheStorage
追蹤的全部命名 Cache
對象對應字符串的數組來resolve。 使用該方法迭代全部 Cache
對象的列表。
Cache
對象經常使用方法
match(request, options)
返回一個 Promise
對象,resolve的結果是跟 Cache
對象匹配的第一個已經緩存的請求。
add(request)
抓取這個URL,檢索並把返回的response對象添加到給定的Cache
對象。這在功能上等同於調用 fetch()
,而後使用 Cache.put()
將response添加到cache
中。
addAll(requests)
抓取一個URL數組,檢索並把返回的response對象添加到給定的Cache
對象。
put(request, response)
同時抓取一個請求及其響應,並將其添加到給定的cache。
keys(request, options)
返回一個Promise
對象,resolve的結果是Cache
對象key值組成的數組。
更多詳細介紹和方法請查閱MDN-CacheStorage、MDN-Cache。
看到這裏你可能又會問,Request???Response???
這裏跟Fetch API有着密切的關係。
Request對象,用來表示資源的請求。
Response對象,用來表示一次請求的響應數據。
咱們打印一下這兩個東西,就很是明瞭了。
好了,接下來繼續咱們的Service Worker。
咱們能夠給 service worker 添加一個 fetch
的事件監聽器,接着調用 event 上的 respondWith()
方法來劫持咱們的 HTTP 響應,而後咱們就能夠進行一波操做了。
// sw.js
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(res => {
return res || fetch(e.request);
})
)
})
複製代碼
這裏的邏輯是這樣的:
fetch
方法向服務端發起請求,並返回請求結果給瀏覽器。最終這裏就簡單實現了緩存靜態資源文件的目的了。
咱們經過修改cacheName
來達到更新緩存資源的目的。因爲瀏覽器判斷sw.js是否更新是經過字節方式,所以修改cacheName
會從新觸發install並緩存資源。此外,在activate事件中,咱們須要檢查cacheName
是否變化,若是變化則表示有了新的緩存資源,原有緩存須要刪除。
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== cacheStorageKey) {
return caches.delete(cache);
}
})
)
})
)
return self.clients.claim();
})
複製代碼
最後咱們能夠在network看到請求資源的信息。
資源來自於Service Worker,時間也是在10ms左右,能夠說是很是快的加載速度了,這樣的體驗對用戶很是友好。
除了緩存靜態資源文件之外,Service Worker還有緩存API數據,進行消息提醒,後臺同步的功能。東西不少,目前還在慢慢探索當中。