serviceworker 離線緩存化
概述
Service Worker
是HTML5 的一個新特性,主要用來做持久的離線緩存。
項目介紹
本項目在第一次安裝serverworker
之後,可以在控制檯看到以下信息:
查看對應請求的靜態資源信息:
可以看到,第一次安裝
serviceworker
時,讀取到的靜態資源並沒有緩存。
刷新瀏覽器之後,我們再看一下這些靜態資源:
可以看到,靜態資源以及被
serviceworker
緩存起來了。
我們再來查看當前serviceworker
安裝情況:
可以看到,
serviceworker
已經處於激活狀態。
最後,看一下離線功能的效果:
是不是很神奇,在離線狀態下我們的頁面也是能夠展示出數據的。
其中,離線的原理就是利用了serviceworker
中,fetch
和cacheStorage
這兩個接口,將請求進行攔截,將響應進行緩存
作用
這個 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
只需要以下幾個步驟:
- 檢測是否支持
serivceworker
首先,檢測當前環境是否支持 service worker
,可以使用 'serviceWorker' in navigator
進行檢測。
- 註冊
如果支持,可以使用 navigator.serviceWorker.register('./sw.js')
,在當前主線程中註冊 service worker
。如果註冊成功,service worker
則在 ServiceWorkerGlobalScope
環境中運行; 需要注意的是: 當前環境無法操作DOM
,且和主線程之間相互獨立(即線程之間不會相互阻塞)。
- 安裝
然後,後臺開始安裝service worker
,一般在此過程中,開始緩存一些靜態資源文件。
- 激活
安裝成功之後,準備進行激活 service worker
,通常在激活狀態下,主要進行緩存清理,更新service worker
等操作。
- 使用
激活成功後,,service worker
就可以控制當前頁面了。需要注意的是,只有在service worker
成功激活後,才具有控制頁面的能力,一般在第一次訪問頁面時,service worker
第一次創建成功,並沒有激活,只有當刷新頁面,再次訪問之後,才具有控制頁面的能力。
源碼實現
該源碼實現了以下幾個功能:
- 強制更新
通過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
})
)
})
請參考: 源碼地址