// html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="index.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
// index.js
/**
* 特性:
* 1. 没有访问 DOM 的权限
* 2. Service Worker 只能被使用在 https 或者本地的 localhost 环境下。
* 3. 以通过 postMessage 接口把数据传递给其他 JS 文件;
* 4. 完全异步,无法使用XHR和localStorage
* 5. 可以使用cache storage/indexedDB缓存数据
* 6. 可以拦截全站请求从而控制你的应用
* 7. 一旦被 install,就永远存在,除非被 uninstall或者dev模式手动删除
*/
// 1. 注册
// 在同一个 Origin 下,我们可以注册多个 Service Worker。但是请注意,这些 Service Worker 所使用的 scope 必须是不相同的。
if('serviceWorker' in window.navigator){
window.addEventListener('load',() => {
// 1.1 注册函数register(path,configuration)接收两个参数
// 第一个是 service worker 文件的路径,请注意:这个文件路径是相对于 Origin ,而不是当前 JS 文件的目录的;第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性。它是 Service Worker 控制的内容的子目录,这个属性所表示的路径不能在 service worker 文件的路径之上,默认是 Serivce Worker 文件所在的目录;
const sw = navigator.serviceWorker;
const killSW = window.killSW || false;
if(!sw){
console.log('The browser does not support serviceWorker');
return;
}
if(!!killSW){
sw.getRegistration('./sw.js').then(reg => {
// 手动注销
reg.unregister();
// 清除缓存
caches && caches.keys && caches.keys().then(keys => {
keys.forEach(key => {
caches.delete(key);
});
});
});
return;
}
sw.register('./sw.js',{scope:'./'}).then(reg => {
// 注册成功:
console.log('success',reg)
// 4. 通讯
const mesChannel = new MessageChannel();
// 4.1 从页面到sw
// 4.1.1/4.4.1 页面发送消息
sw.controller && sw.controller.postMessage('this message is from page,to sw!',[mesChannel.port2]);
// 4.4 使用message channel通信
// 4.4.4 使用MessageChannel接收sw发送到页面的消息
mesChannel.port1.onmessage = e => {
console.log(e.data);
}
}).catch(err => {
// 注册失败
console.log('Registration for serviceWorker failed!' + err)
})
// 当我们在注册 Service Worker 时,如果使用的 scope 不是 Origin ,那么navigator.serviceWorker.controller 会为 null。
// 这种情况下,我们可以使用 reg.active 这个对象下的 postMessage 方法,reg.active 就是被注册后激活 Serivce Worker 实例。
// 但是,由于 Service Worker 的激活是异步的,因此第一次注册 Service Worker 的时候,Service Worker 不会被立刻激活, reg.active 为 null,系统会报错。我采用的方式是返回一个 Promise ,在 Promise 内部进行轮询,如果 Service Worker 已经被激活,则 resolve 。
/**
sw.register('./sw.js',{scope:'./'}).then(reg => {
// 注册成功:
console.log('success',reg)
return new Promise(resolve =>{
const timer = setInterval(() => {
if(reg.active){
clearInterval(timer);
resolve(reg.active);
}
},100)
})
}).then(sw => {
sw.postMessage('this message is from page,to sw!');
}).catch(err => {
// 注册失败
console.log('Registration for serviceWorker failed!' + err)
});
*/
// 4.2.1 页面接收从sw发送的消息
sw.addEventListener('message',e => {
console.log(e.data);
});
})
}
// sw.js
// 在默认情况下,Service Worker 必定会每24小时被下载一次,
// 如果下载的文件是最新文件,那么它就会被重新注册和安装,但不会被激活,
// 当不再有页面使用旧的 Service Worker 的时候,它就会被激活。
// 2. 注册之后监听安装事件install
self.addEventListener('install',e => {
console.log('sevice worker install...');
// 5.1 缓存指定静态资源
const staticCache = [
'./index.js',
];
// CacheStroage 在浏览器中的接口名是 caches ,
// 我们使用 caches.open 方法新建或打开一个已存在的缓存;
// cache.addAll 方法的作用是请求指定链接的资源并把它们存储到之前打开的缓存中。
// 由于资源的下载、缓存是异步行为,所以我们要使用事件对象提供的 event.waitUntil 方法,它能够保证资源被缓存完成前 Service Worker 不会被安装完成,避免发生错误。
e.waitUntil(
caches.open('sw_demo_v1').then(cache => {
return cache.addAll(staticCache);
})
);
})
const cacheNames = ['sw_demo_v2']; // cache storage白名单
// 3. 安装成功后,监听激活事件
self.addEventListener('activate',e => {
console.log('sevice worker activate...');
// 5.3 清理cache storage
e.waitUntil(
caches.keys().then(keys => {
return Promise.all([
keys.map(key => {
if(!cacheNames.includes(key)){
return caches.delete(key); // 删除不在白名单的cache storage
}
})
]);
}).then(() =>{
// 立即接管所有页面
self.clients.claim();
})
);
})
// 4.1.2/4.4.2 接收页面发送的消息
self.addEventListener('message',e => {
console.log(e.data);
// 4.2 从sw发送消息到页面
// 4.2.1 sw发送消息
e.source.postMessage('this message is from sw.js,to page!');
// 4.4.3 使用MessageChannel,从sw发送消息到页面
e.ports[0].postMessage('this message is from sw.js,to page!Use messageChannel!');
})
// 4.3 以上的通讯方式sw只能对消息来源的页面发送消息,以下可以不受限制发送消息
self.clients.matchAll().then(clientArr => {
console.log('sw管理的页面:',clientArr);
if(clientArr.length){
// 对页面发送消息
clientArr[0].postMessage('this message is from sw.js,to client[0]!');
}
});
// 5. 缓存资源
/**
* 注意:
* 1. 当用户第一次访问页面的时候,资源的请求是早于 Service Worker 的安装的,所以静态资源是无法缓存的;只有当 Service Worker 安装完毕,用户第二次访问页面的时候,这些资源才会被缓存起来;
* 2. Cache Stroage 只能缓存静态资源,所以它只能缓存用户的 GET 请求;post请求的数据可以缓存到indexedDB
* 3. Cache Stroage 中的缓存不会过期,但是浏览器对它的大小是有限制的,所以需要我们定期进行清理;
*/
// 5.2 动态缓存静态资源:监听fetch事件
// 页面的路径不能大于 Service Worker 的 scope,不然 fetch 事件是无法被触发的。
self.addEventListener('fetch',e => {
const request = e.request;
// 非GET请求
if(request.method !== 'GET'){
e.respondWith(
// ...
);
return;
}
// HTML页面请求
if(request.headers.get('Accept').indexOf('text/html') !== -1){
e.respondWith(
// ...
);
return;
}
// get接口请求
if(request.headers.get('Accept').indexOf('application/json') !== -1){
e.respondWith(
// ...
);
return;
}
// GET请求 且 非页面请求时 且 非get接口请求(一般请求静态资源)
// 使用事件对象提供的 respondWith 方法,它可以劫持用户发出的 http 请求
e.respondWith(
// 使用用户的请求对 Cache Stroage 进行匹配,如果匹配成功,则返回存储在缓存中的资源;
caches.match(request).then(res => {
return res ||
// 如果匹配失败,则向服务器请求资源返回给用户,并使用 cache.put 方法把这些新的资源存储在缓存中。因为请求和响应流只能被读取一次,所以我们要使用 clone 方法复制一份存储到缓存中,而原版则会被返回给用户
fetch(request).then(response => {
const responseCopy = response.clone();
caches.open('sw_demo_v2').then(cache => {
cache.put(request,responseCopy);
});
return response;
}).catch(err => {
console.log(err);
});
})
);
})