概述
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 静态资源文件
缓存收益和成本
- 收益
- 省去建立tcp的连接时长,加快首屏加载速度
- 减少静态资源服务器的负载
- 成本
- 数据不一致问题(更新策略)
- 代码维护成本(缓存文件)
项目演示
本项目在第一次安装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
})
)
})
复制代码
请参考: 源码地址