-
用 serviceWorker 做了什么事情
-
serviceWorker 介绍
-
为什么用 serviceWorker
-
serviceWorker 怎么用
-
总结
用 serviceWorker 做了什么事情
小说阅读器页面以及资源缓存
加缓存之前:浏览器非首次跳阅读器 -> 请求网络资源 -> 渲染展示
加缓存之后:浏览器非首次跳阅读器 -> 直接从缓存取资源 -> 渲染展示
serviceWorker 介绍
serviceWorker 是 PWA 重要的一部分
PWA:Progressive Web App, 渐进式增强 WEB 应用,包含以下几种技术:
-
App Manifest 网页加桌
-
Service Worker 缓存
-
Cache API、IndexedDB 离线缓存
-
Notifications API 通知
-
Push API 推送
-
Background Sync 后台同步(关闭网页后仍可发送请求,无网下维持请求待联网后请求)
serviceWorker 特性
-
浏览器在后台运行的事件驱动 worker 脚本
-
worker: 无法访问 DOM,不会对主进程造成阻塞,支持 indexedDB、postMessage
-
有自己的生命周期:下载、安装、激活
-
可以在浏览器和网络之间建立一个中间层,拦截和处理网络请求
-
激活后可以拦截网络请求并返回缓存的响应,从而细粒度地控制缓存资源
-
只能用在在 https 域名下,本地开发可以用 http:localhost 或者 http://127.0.0.1
为什么用 serviceWorker
后端接口缓存
HTTP 协议可以定义一个响应资源应该何时、如何被缓存以及缓存多长时间
后端接口返回对应相应头给到浏览器,浏览器根据相应规则去缓存对应资源
浏览器自带缓存
浏览器会把请求过的资源放在内存、硬盘中缓存起来
重复刷新网站会直接用缓存资源
为什么用 serviceWorker
接口缓存由后端根据实际情况去加缓存策略;
-
Nginx 静态资源缓存需要写死在 nginx 配置里,不够灵活;
-
浏览器自带缓存未提供控制接口,无法自由控制;
-
serviceWorker 提供完整的编程接口和监听事件,可自由发挥实现任意资源的缓存, 灵活可控。
serviceWorker 怎么用
serviceWorker 作用域
- serviceWorker 默认作用域就是注册时候的 path
- 举例1:serviceWorker 注册的 path 为 http://127.0.0.1:8005/sw.js,则 scope 默认为 /
- 举例2:serviceWorker 注册的 path 为 http://127.0.0.1:8005/sw.js/read/sw.js,则 scope 默认为 /read/
- 可以通过 register() 中传入 {scope: '/read/'} 指定作用域,指定作用域不允许超过最大作用域范围(注册 path)
- 举例1:注册 path 为 http://127.0.0.1:8005/sw.js,则 scope 可传任意目录
- 举例2:注册 path 为 http://127.0.0.1:8005/read/sw.js,则 scope 可传 /read/ /read/a/,不可传 /read2/
建议:注册 path 为根目录,根据需要指定 scope 参数
举例:相同域名的独立子站:https://xxx/a 和 https://xxx/b 是两个站点
指定作用域 scope / ./ /read/ 区别
- / 根目录
serviceWorker http://127.0.0.1:8005/ 可控制域名下所有页面(控制:可以处理这些页面里面的资源请求和网络请求)
http://127.0.0.1:8005/read/22324417000586202 ✅
http://127.0.0.1:8005/blank/22324417000586202 ✅
- /read/ 指定目录
serviceWorker http://127.0.0.1:8005/read/ 可控制 read 目录下所有页面
http://127.0.0.1:8005/read/22324417000586202 ✅
http://127.0.0.1:8005/blank/22324417000586202 ❎
- ./ 当前访问目录
产生多个 serviceWorker
http://127.0.0.1:8005/read/22324417000586202 serviceWorker http://127.0.0.1:8005/read/
http://127.0.0.1:8005/blank/22324417000586202 serviceWorker http://127.0.0.1:8005/blank/
http://127.0.0.1:8005/read serviceWorker http://127.0.0.1:8005/
http://127.0.0.1:8005 serviceWorker http://127.0.0.1:8005/
作用域污染
多个 serviceWorker 会产生作用域污染,即一个页面被多个 serviceWorker 控制。
Devtools 中,可以手动清除指定的 serviceWorker,在线上环境已经安装了如何处理?
可以先注销掉错误的 serviceWorker 再注册新的。
为什么放在 window.onload 内执行?
-
启动这个额外的线程会增加对 CPU 时间和内存的争用
-
如果该线程不是空闲的,会争用网络下载资源,不要通过同时下载次要资源来破坏关键资源。参考文章:web.dev/service-wor…
-
serviceWorker 第1次注册并不会拦截 fetch 请求
为什么第1次注册不拦截 fetch 请求?
因为在 serviceWorker 的注册是一个异步的过程,在激活完成后当前页面的请求都已经发送完成,时机太晚。
实际请求速度:
- 第 1 次进页面注册 serviceWorker - 请求速度无变化
- 第 2 次进页面 serviceWorker 拦截请求并且把请求缓存起来 - 请求速度无变化
- 第 3 次进页面 serviceWorker 拦截请求并直接使用上次的缓存 - 请求速度变快
安装激活
拦截请求
缓存策略
fetch 请求拦截
serviceWorker
cache 缓存
缓存的资源
serviceWorker 怎么更新
- 下载
- 每次进入页面会重新下载 serviceWorker.js
- 每 24 小时必定会下载一次 serviceWorker.js(自定义计时器调 update() 可定期更新)
- 在 service worker 上的一个事件被触发并且过去 24 小时没有被下载会触发新的下载
- 安装更新
- 方法1:注册 path 地址的变更,navigator.serviceWorker.register('./sw.js?v=20190401235959')
- 方法2:下载后按照字节比对两次的 servicework.js,发现不一致就重新安装,修改版本标识更新
- 激活
- 第1次注册 serviceWorker 激活后,页面的请求不会通过 worker,只有在刷新页面后才会通过 serviceWorker 代理请求。
- 安装完毕后如果有旧版 serviceWorker 在运行,会进入 waiting 状态,直到不再有页面使用旧的 serviceWorker 才激活。可以使用 skipWaiting() 跳过等待直接激活。
- 使用 self.clients.claim() 可同步激活全部终端。
- 激活后可根据策略删除旧版缓存。
waitUntil()
作用:延长生命周期事件的执行时间,等待传递的 Promise 被成功 resolve。
在 install 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 installing 状态,直到传递的 Promise 被成功地 resolve。作用:等待核心静态资源缓存完毕。
在 activate 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 activating 状态,直到传递的 Promise 被成功地 resolve。作用:删除过期的缓存条目。
skipWaiting()
作用:同时打开多个网址,即存在多个终端。正常需要等所有的终端都关闭之后(全部关闭再新打开1个网站),才能激活新的 serviceWorker。如果不想等,检测到更新就直接激活新的 serviceWorker,用 skipWaiting。
风险:skipWaiting 意味着新 Service Worker 可能会控制使用较旧 serviceWorker 控制的页面。这意味着页面提取的部分数据将由旧 serviceWorker 处理,而新 serviceWorker 处理后来提取的数据。如果这会导致问题,则不要使用 skipWaiting()。
为什么用:监听 onload,时机很后面了,此次所有请求都由旧 serviceWorker 处理。但是我需要立马进入激活,让下次进来全部走新的(进入激活判断版本不一致删除缓存)。除非有需要否则一般不用更新。
claim()
问题:使用 skipWaiting 直接激活 serviceWorker,可能会出现其他终端还没有受当前终端激活的serviceWorker 控制的情况,切回其他终端之后,serviceWorker 控制页面的效果可能不符合预期。
解决:调用 self.clients.claim() 立马作用于 scope 内的所有终端。
为啥没用:不是都要加这个,要看是否有激活 serviceWorker 后马上控制所有终端的需要。
用法:
event.waitUntil(
self.clients.claim().then(() => {})
)
总结
-
serviceWorker 特性:异步独立线程,生命周期监听,请求拦截,缓存读写 API
-
网站资源缓存:
- 接口请求后端控制
- 浏览器自带缓存(图片资源)
- html、js、css等资源请求通过 serviceWorker 控制
- serviceWorker 缓存:
- onload 里面执行注册
- 第3次刷新才会用到缓存加速
- skipWaiting 跳过等待
- serviceWorker 更新
- document、外部依赖 sdk 先用缓存后更新,js、css、font 一直用缓存
- 检测是否支持:html5test.com