概述
Service Worker
是HTML5 的一個新特性,主要用來做持久的離線快取。
為什麼要使用ServiceWorker
在公司業務中,因為經常要處理效能優化方面的需求,使用傳統的效能優化手段,滿足了大多數業務場景。但是,如果目標使用者手機效能以及網路普遍較差的情況下(例如東南亞、印度等海外市場),瓶頸就在於
DNS查詢
,TCP
的建立時間,採用常規的優化手段就顯得捉襟見肘。此時,我們專案組有嘗試採用離線快取方案,即將靜態資源快取到本地,通過攔截代理請求,讀取本地檔案,加快訪問速度。
ServiceWorker的目的
這個 API 的唯一目的就是解放主執行緒,
Web Worker
是脫離在主執行緒之外的,將一些複雜的耗時的活交給它幹,完成後通過postMessage
方法告訴主執行緒,而主執行緒通過onMessage
方法得到Web Worker
的結果反饋。
功能和特性
Service Worker
擁有自己獨立的worker
執行緒,獨立於當前網頁執行緒- 離線快取靜態資源
- 攔截代理請求和響應
- 可自定義響應內容
- 可以通過
postMessage
向主執行緒傳送訊息 - 無法直接操作DOM
- 必須在HTTPS環境下工作或 localhost / 127.0.0.1 (自身安全機制)
- 通過
Promise
非同步實現 Service Worker
安裝(installing
)完成後,就會一直存在,除非手動解除安裝(unregister
)
生命週期
Service Worker
的生命週期完全獨立於網頁
- 註冊 (
register
) - 安裝 (
install
) - 啟用 (
activate
)
通常使用
service worker
只需要以下幾個步驟:
- 1. 檢測是否支援
serivceworker
首先,檢測當前環境是否支援 service worker
,可以使用 'serviceWorker' in navigator
進行檢測。
- 2. 註冊(register)
如果支援,可以使用 navigator.serviceWorker.register('./sw.js')
,在當前主執行緒中註冊 service worker
。如果註冊成功,service worker
則在 ServiceWorkerGlobalScope
環境中執行; 需要注意的是: 當前環境無法操作DOM
,且和主執行緒之間相互獨立(即執行緒之間不會相互阻塞)。
- 3. 安裝(install)
然後,後臺開始安裝service worker
,一般在此過程中,開始快取一些靜態資原始檔。
- 4. 啟用(active)
安裝成功之後,準備進行啟用 service worker
,通常在啟用狀態下,主要進行快取清理,更新service worker
等操作。
- 5. 使用(activing)
啟用成功後,,service worker
就可以控制當前頁面了。需要注意的是,只有在service worker
成功啟用後,才具有控制頁面的能力,一般在第一次訪問頁面時,service worker
第一次建立成功,並沒有啟用,只有當重新整理頁面,再次訪問之後,才具有控制頁面的能力。
- 6. 解除安裝(unregister)
快取顆粒化
- 快取 *.html 靜態資原始檔
快取收益和成本
專案演示
本專案在第一次安裝serverworker
之後,可以在控制檯看到以下資訊:
檢視對應請求的靜態資源資訊:
重新整理瀏覽器之後,我們再看一下這些靜態資源:可以看到,第一次安裝
serviceworker
時,讀取到的靜態資源並沒有快取。
可以看到,靜態資源以及被
serviceworker
快取起來了。
我們再來檢視當前serviceworker
安裝情況:
可以看到,
serviceworker
已經處於啟用狀態。
最後,看一下離線功能的效果:
是不是很神奇,在離線狀態下我們的頁面也是能夠展示出資料的。 其中,離線的原理就是利用了
serviceworker
中,fetch
和cacheStorage
這兩個介面,將請求進行攔截,將響應進行快取
原始碼實現
該原始碼實現了以下幾個功能:
- 強制更新
通過
self.skipWaiting()
,如果檢測到新的service worker
檔案,就會立即替換掉舊的。 - 快取靜態資源
cache.addAll(cacheFiles)
通過這個介面實現 - 攔截請求
通過監聽
fetch
事件,可以攔截當前頁所有請求self.addEventListener('fetch',function(e){})
- 快取響應
將響應內容加入快取
cache.put(evt.request, response)
// 快取靜態資原始檔列表
let cacheFiles = [
'./test.js',
'./index.html',
'./src/img/yy.png'
]
// serviceworker使用版本
let __version__ = 'cache-v2'
// 快取靜態資源
self.addEventListener('install', function (evt) {
// 強制更新sw.js
self.skipWaiting()
evt.waitUntil(
caches.open(version).then(function (cache) {
return cache.addAll(cacheFiles)
})
)
})
// 快取更新
self.addEventListener('active', function (evt) {
evt.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheName !== version) {
return caches.delete(cacheName)
}
})
)
})
)
})
// 請求攔截
self.addEventListener('fetch', function (evt) {
console.log('處理fetch事件:', evt.request.url)
evt.respondWith(
caches.match(evt.request).then(function (response) {
if (response) {
console.log('快取匹配到res:', response)
return response
}
console.log('快取未匹配對應request,準備從network獲取', caches)
return fetch(evt.request).then(function (response) {
console.log('fetch獲取到的response:', response)
caches.open(version).then(function (cache) {
cache.put(evt.request, response)
return response
})
})
}).catch(function (err) {
console.error('fetch 介面錯誤', err)
throw err
})
)
})
複製程式碼
請參考: 原始碼地址