serviceworker 离线缓存化(附源码)
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
})
)
})
请参考: 源码地址