一个简单的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
| ... <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> ...
|

先缓存文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const VERSION = 0;
const CACHE_NAME = 'sw-cache-v' + VERSION;
const CACHE_URLS = [ './index.html', './img/1.jpg', ];
addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME).then(function (cache) { return cache.addAll(CACHE_URLS); }) ) skipWaiting(); })
|

这样就将文件缓存在浏览器了,接下来就是监听网络请求了,如果是缓存的请求,就直接返回缓存的文件,如果是网络请求,就返回网络请求的文件。
监听网络请求,实现离线访问功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ... addEventListener('fetch', event => {
if(new URL(event.request.url).origin != location.origin) { return; } event.respondWith( caches.match(event.request) ) })
|

更新缓存
在说明更新缓存的时候,我需要先说明它的书写规范
- 不要给 service-worker.js
设置不同的名字
- 不要给 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 => { 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更新过程

完整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', ];
addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME).then(function (cache) { return cache.addAll(CACHE_URLS); }) ) skipWaiting(); })
addEventListener('activate', event => { event.waitUntil( caches.keys().then(function (keys) { keys.forEach(function (key) { if (key !== CACHE_NAME) { return caches.delete(key); } }) }) ) })
addEventListener('fetch', event => {
if(new URL(event.request.url).origin != location.origin) { return; } event.respondWith( caches.match(event.request) ) })
|