什么是 Service Worker ?
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 实现缓存功能一般分为三个步骤:
- 首先需要先注册 Service Worker
- 然后监听到 install 事件以后就可以缓存需要的文件
- 在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存
- 存在缓存的话就可以直接读取缓存文件
- 不存在缓存就去请求数据
以下是这个步骤的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| if (navigator.serviceWorker) { navigator.serviceWorker .register("sw.js") .then(function (registration) { console.log("service worker 注册成功"); }) .catch(function (err) { console.log("servcie worker 注册失败"); }); }
self.addEventListener("install", (e) => { e.waitUntil( caches.open("my-cache").then(function (cache) { return cache.addAll(["./index.html", "./index.js"]); }) ); });
self.addEventListener("fetch", (e) => { e.respondWith( caches.match(e.request).then(function (response) { if (response) { return response; } console.log("fetch source"); }) ); });
|
注册 ServiceWorker
ServiceWorker
的注册是通过navigator.serviceWorker.register
来完成的;
- 第一个参数是
ServiceWorker
的脚本地址
- 第二个参数是一个配置对象,目前只有一个属性
scope
,用来指定ServiceWorker
的作用域,它的默认值是ServiceWorker
脚本所在目录。
1 2 3 4 5 6 7 8 9 10 11
| js复制代码if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', { scope: '/' }).then(function (registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker registration failed: ', err); }); }
|
Service Worker 生命周期
安装
安装阶段是在ServiceWorker
注册成功之后,浏览器开始下载ServiceWorker
脚本的阶段;
这个阶段是一个异步的过程,我们可以在install
事件中监听它,它的回调函数会接收到一个event
对象;
我们可以通过event.waitUntil
来监听它的完成状态,当它完成之后,我们需要调用event.waitUntil
的参数,这个参数是一个Promise
对象,当这个Promise
对象完成之后,浏览器才会进入下一个阶段。
1 2 3 4 5 6
| self.addEventListener('install', function (event) { console.log('install'); event.waitUntil( ); });
|
激活
激活阶段是在安装完成之后,浏览器开始激活ServiceWorker
的阶段;
这个阶段也是一个异步的过程,我们可以在activate
事件中监听它,它的回调函数会接收到一个event
对象;
不同于安装阶段,激活阶段不需要等待event.waitUntil
的传递的Promise
对象完成,它会立即进入下一个阶段。
1 2 3 4 5 6
| self.addEventListener('activate', function (event) { console.log('activate'); event.waitUntil( ); });
|
运行
运行阶段是在激活完成之后,ServiceWorker
开始运行的阶段;
这个阶段是一个长期存在的过程,我们可以在fetch
事件中监听它,它的回调函数会接收到一个event
对象;
任何请求拦截都是在这个阶段进行的,我们可以在这个阶段中对请求进行拦截,然后返回我们自己的响应。
1 2 3
| self.addEventListener('fetch', function (event) { console.log('fetch'); });
|
上面我们已经成功的注册了ServiceWorker
,并且它已经进入了运行阶段,那么我们就可以在这个阶段中对请求进行拦截了
这里我们就不举例了, 感兴趣的可以看下 https://juejin.cn/post/7165893682132959245?searchId=202404062005049010FFAF9B9C9C30670C#heading-3
Service Worker 监听事件
看看ServiceWorker
的事件列表:
install
:安装事件,当ServiceWorker
安装成功后,就会触发这个事件,这个事件只会触发一次。
activate
:激活事件,当ServiceWorker
激活成功后,就会触发这个事件,这个事件只会触发一次。
fetch
:网络请求事件,当页面发起网络请求时,就会触发这个事件。
push
:推送事件,当页面发起推送请求时,就会触发这个事件。
sync
:同步事件,当页面发起同步请求时,就会触发这个事件。
message
:消息事件,当页面发起消息请求时,就会触发这个事件。
messageerror
:消息错误事件,当页面发起消息错误请求时,就会触发这个事件。
error
:错误事件,当页面发起错误请求时,就会触发这个事件。
message
,messageerror
,error
这三个事件,就是主线程和Worker
之间的通信
push
和sync
这两个事件,感兴趣的自己去查阅资料~
Service Worker 缓存
我们可以通过ServiceWorker
来缓存我们的静态资源,这样就可以离线访问我们的页面了。
ServiceWorker
的缓存是基于CacheStorage
的,它是一个Promise
对象,我们可以通过caches
来获取它
1 2 3
| caches.open('my-cache').then(function (cache) { });
|
CacheStorage
提供了一些方法,我们可以通过这些方法来对缓存进行操作;
添加缓存
我们可以通过cache.put
来添加缓存,它接收两个参数,第一个参数是Request
对象,第二个参数是Response
对象;
1 2 3
| caches.open('my-cache').then(function (cache) { cache.put(new Request('/'), new Response('Hello World')); });
|
获取缓存
我们可以通过cache.match
来获取缓存,它接收一个参数,这个参数可以是Request
对象,也可以是URL
字符串;
1 2 3 4 5
| caches.open('my-cache').then(function (cache) { cache.match('/').then(function (response) { console.log(response); }); });
|
删除缓存
我们可以通过cache.delete
来删除缓存,它接收一个参数,这个参数可以是Request
对象,也可以是URL
字符串;
1 2 3 4 5
| caches.open('my-cache').then(function (cache) { cache.delete('/').then(function () { console.log('删除成功'); }); });
|
清空缓存
我们可以通过cache.keys
来获取缓存的key
,然后通过cache.delete
来删除缓存;
1 2 3 4 5 6 7
| caches.open('my-cache').then(function (cache) { cache.keys().then(function (keys) { keys.forEach(function (key) { cache.delete(key); }); }); });
|
ServiceWorker
的缓存策略是基于fetch
事件的,我们可以在fetch
事件中监听请求,然后对请求进行拦截,然后返回我们自己的响应;
1 2 3 4 5 6 7 8 9 10
| self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request).then(function (response) { if (response) { return response; } return fetch(event.request); }) ); });
|
缓存资源
我们可以通过cache.addAll
来缓存一些资源;
通常我们会在install
事件中缓存一些资源,因为install
事件只会触发一次,并且会阻塞activate
事件,所以我们可以在install
事件中缓存一些资源,然后在activate
事件中删除一些旧的资源;
通常情况下,我们会在activate
事件中删除旧的缓存,然后在install
事件中缓存新的资源;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| self.addEventListener('install', function (event) { event.waitUntil( caches.open('my-cache').then(function (cache) { return cache.addAll([ '/', '/index.css', '/axios.js', '/index.html' ]); }) ); });
self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request).then(function (response) { if (response) { return response; } return fetch(event.request); }) ); });
self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all( cacheNames.map(function (cacheName) { if (cacheName !== 'my-cache') { return caches.delete(cacheName); } }) ); }) ); });
|
参考文章
- https://www.whyknown.com/post/698
- https://juejin.cn/post/7165893682132959245?searchId=202404062005049010FFAF9B9C9C30670C#heading-17