抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

一个简单的ServiceWorker应用

目标

实现在离线状态下能正常加载页面以及本域的图片等资源

下载素材包

点击下载素材包

注册sw.js文件

新建sw.js文件
创建后目录结构如下:
├─index.html
├─sw.js
├─img
└1.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- index.html -->
...
<script>
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(registration => {
console.log('ServiceWorker成功注册在域: ', registration.scope);
}).catch(err => {
console.log('ServiceWorker注册失败: ', err);
}
);
}
}
);
</script>
...

博客ServiceWorker注册成功

先缓存文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// sw.js
const VERSION = 0;
// 缓存版本
const CACHE_NAME = 'sw-cache-v' + VERSION;
// 缓存空间名字
const CACHE_URLS = [
'./index.html',
'./img/1.jpg',
];
// 需要缓存的url

addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
})
)
skipWaiting();
// 加上这句话,可以让sw立即生效,从而立刻删除旧的sw文件
})

博客ServiceWorker缓存成功
这样就将文件缓存在浏览器了,接下来就是监听网络请求了,如果是缓存的请求,就直接返回缓存的文件,如果是网络请求,就返回网络请求的文件。

监听网络请求,实现离线访问功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// sw.js
...
addEventListener('fetch', event => {
// fech事件用于接收fetch请求来选择走缓存还是走网络

if(new URL(event.request.url).origin != location.origin) {
// 过滤非本站的资源
return;
}

event.respondWith(
caches.match(event.request)
)
})

博客ServiceWorker脱机加载成功

更新缓存

在说明更新缓存的时候,我需要先说明它的书写规范

  1. 不要给 service-worker.js 设置不同的名字
  2. 不要给 service-worker.js 设置缓存。Cache-control: no-store 是比较安全的。

浏览器的更新机制

SW的waiting状态

注册navigator.serviceWorker.register()时有两种情况:

  • 如果没有活跃(activate)的SW,那就直接安装且激活。
  • 如果已经有SW安装着,那么就和新的SW比较。如果没有差别则不做任何操作,如果有差别,那么就安装新的SW(执行install阶段),之后进入waiting状态。等待老的SW所有页面被关闭后,才会执行activate阶段,从而接管页面。

    由于浏览器是等待新的页面渲染之后才销毁旧的页面,因此简单的切换页面或者刷新是不能使得SW进行更新,只能关闭页面刷新。默认的更新是一种比较温和的做法,但让用户关闭页面并不是程序员能控制的。
    假设我们提供了一次重大升级,希望新的 SW 尽快接管页面,应该怎么做呢?

方法一:skipWaiting()

在遭遇突发情况时,很容易想到通过“插队”的方式来解决问题,现实生活中的救护车消防车等特种车辆就采用了这种方案。SW 也给程序员提供了实现这种方案的可能性,那就是在 SW 内部的 skipWaiting() 方法。

1
2
3
4
addEventListener('install', event => {
self.skipWaiting()
// 预缓存其他静态内容
})

虽然这个方法显得很简单,但是它的实现还是有很多缺点的

比如有文件sw.v1.js和sw.v2.js。浏览器默认先异步加载sw.v1.js,缓存了前半部分文件。但是假如sw.v2.js应用了skipWaiting方法,那么sw.v1.js就会被强制退休,sw.v2.js立马接管页面。从而导致一个页面前半部分在sw.v1.js后半部分在sw.v2.js,在脱机状态浏览器就会找不到某些文件而报错

除非你能保证同一个页面在两个版本的 SW 相继处理的情况下依然能够正常工作,才能使用这个方案。

SW的其它更新机制
  • 浏览器每24小时自动更新一次Service Worker
    写个js自己试试就知道了
  • 注册新的 Service Worker,带上版本号,如:/sw.js?v=201807021920
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const VERSION = 1;
// 修改上面的缓存版本
const CACHE_NAME = 'sw-cache-v' + VERSION;
// 缓存空间名字
...
addEventListener('activate', event => {
// Service Worker 激活时,删除旧的缓存
event.waitUntil(
caches.keys().then(function (keys) {
keys.forEach(function (key) {
if (key !== CACHE_NAME) {
// 遍历所有缓存,如果不是当前版本,则删除
return caches.delete(key);
// 删除旧的缓存
}
})
})
)
})
...
  • 手动更新registration.update()
  • 逐字节对比新的sw文件和旧的sw文件,有区别才更新

Service Worker更新后通知用户

Service Worker更新过程
博客ServiceWorker更新过程

完整sw.js代码

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
39
40
41
42
43
44
45
46
47
48
49
const VERSION = 0;
// 缓存版本
const CACHE_NAME = 'sw-cache-v' + VERSION;
// 缓存空间名字
const CACHE_URLS = [
'./',
'./index.html',
'./img/1.jpg',
];
// 需要缓存的url

addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(CACHE_URLS);
})
)
skipWaiting();
// 加上这句话,可以让sw立即生效,从而立刻删除旧的sw文件
})

addEventListener('activate', event => {
// Service Worker 激活时,删除旧的缓存
event.waitUntil(
caches.keys().then(function (keys) {
keys.forEach(function (key) {
if (key !== CACHE_NAME) {
// 遍历所有缓存,如果不是当前版本,则删除
return caches.delete(key);
// 删除旧的缓存
}
})
})
)
})

addEventListener('fetch', event => {
// fech事件用于接收fetch请求来选择走缓存还是走网络

if(new URL(event.request.url).origin != location.origin) {
// 过滤非本站的资源
return;
}

event.respondWith(
caches.match(event.request)
)
})

评论




站点访问量 Loading… 站点访客数 Loading…