什么是 Service Worker?

什么是 Service Worker ?

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

Service Worker 实现缓存功能一般分为三个步骤:

  1. 首先需要先注册 Service Worker
  2. 然后监听到 install 事件以后就可以缓存需要的文件
  3. 在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存
    • 存在缓存的话就可以直接读取缓存文件
    • 不存在缓存就去请求数据

以下是这个步骤的实现:

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
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function (registration) {
console.log("service worker 注册成功");
})
.catch(function (err) {
console.log("servcie worker 注册失败");
});
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
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:错误事件,当页面发起错误请求时,就会触发这个事件。

messagemessageerrorerror这三个事件,就是主线程和Worker之间的通信

pushsync这两个事件,感兴趣的自己去查阅资料~

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);
}
})
);
})
);
});

参考文章

  1. https://www.whyknown.com/post/698
  2. https://juejin.cn/post/7165893682132959245?searchId=202404062005049010FFAF9B9C9C30670C#heading-17

什么是 Service Worker?
http://example.com/2024/04/06/什么是ServiceWorker/
作者
weirdo
发布于
2024年4月6日
更新于
2024年4月6日
许可协议