由於專案中有個問題涉及到了Service Worker,所以找了時間去研究了一下PWA。也趁此寫一篇文章總結一下。
前言:PWA作為今年最火熱的技術概念之一,對提升Web應用的安全、效能和體驗有著很大的意義,非常值得我們去了解與學習。
什麼是PWA
PWA,全稱Progressive Web App,即漸進式WEB應用, 是提升 Web App 的體驗的一種新方法,能給使用者原生應用的體驗。它的優勢主要體現在:
- 可在離線或網路較差的環境下正常開啟頁面。
- 安全(HTTPS)。
- 保持最新(及時更新)。
- 支援安裝(新增到主螢幕)和訊息推送。
- 向下相容,在不支援相關技術的瀏覽器中仍可正常訪問。
PWA本身其實是一個概念集合,它不是指某一項技術,而是通過一系列的Web技術與Web標準來優化Web App的安全、效能和體驗。其中涉及到的一些技術概念包括但不限於:
- Web App Manifest
- Service Worker
- Cache API 快取
- Push&Notification 推送與通知
- Background Sync 後臺同步
本文主要講一下Service Worker相關的東西。
Service Worker
1. 什麼是Service Worker?
Service worker是一個註冊在指定源和路徑下的事件驅動worker。它採用JavaScript控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地快取資源。你可以完全控制應用在特定情形(最常見的情形是網路不可用)下的表現。
Service worker執行在worker上下文,因此它不能訪問DOM。相對於驅動應用的主JavaScript執行緒,它執行在其他執行緒中,所以不會造成阻塞。它設計為完全非同步,同步API(如XHR和localStorage)不能在service worker中使用。
下圖展示普通Web App與新增了Service Worker的Web App在網路請求上的差異:
2. 使用Service Worker
2.1. 註冊Service Worker
在index.js檔案裡面註冊Service Worker。
// 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標準入門)
2.2. 使用Service Worker
Service Worker的生命週期
當我們註冊了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);
})
)
})
複製程式碼
這裡的邏輯是這樣的:
- 瀏覽器發起請求,請求各類靜態資源(html/js/css/img);
- Service Worker攔截瀏覽器請求,並查詢當前cache;
- 若存在cache則直接返回,結束;
- 若不存在cache,則通過
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左右,可以說是非常快的載入速度了,這樣的體驗對使用者非常友好。
2.4. Service Worker其他功能
除了快取靜態資原始檔以外,Service Worker還有快取API資料,進行訊息提醒,後臺同步的功能。東西很多,目前還在慢慢探索當中。