最近讨论 PWA 的好多啊,PWA 中有个非常重要的角色是 Service Worker 。刚写这篇文章的时候粗心一看,把现在发的 Working Draft 的发布时间以为就是 Service Worker 的发布时间,被棕神挑刺啦。spec 仓库的第一个 commit 是 2013 年 2 月 7 日,W3C 发第一个 Service Worker 的 public working draft 是 2014 年 5 月 8 日,第一个实现 Service Worker 的是 Chrome 40 (2015 年 1 月 21 日)
以下将 Service Worker 简称为 SW。
SW 的功能略有耳闻,在 MDN 上它被描述为,「对资源缓存和自定义的网络请求进行控制的一个好的统筹机制」,同时他还具有「访问推送通知和后台同步API」的功能。
听起来很神奇,事实上看起来也很神奇,Twitter 上个月更新了样式,大家都看到了推特变成了圆头像圆角,然而推特还使用了新的 SW ,在允许推特使用 Notification 的情况下,推特可以在你 Chrome 开启的时候而没有打开网页的时候给你推送你所关注的内容(传送门),有一种 javascript 在关闭网页后,甚至没有打开网页的情况下,就能在后台运行的感觉。
但事实上并非如此
先编写一个最简单的 SW
从零创建 Service Worker,只需四步
第一步:我们需要一张 index.html
<html>
<head>
<title>My first Service Worker test</title>
</head>
<body>
Hello, Service Worker!
</body>
</html>
第二步:我们需要一个 sw.js ,它就是我们的 SW
console.log('When you see this, we have successfully registered the SW!');
第三步:我们需要在刚才的网页上注册,我们可以在网页中的 js 引入注册,在这里我直接写在了 index.html 的 script 标签
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' }).then(function(reg) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
</script>
最后一步:将上边的两个文件放在一个文件夹里,从这个文件夹起个服务器
打开浏览器,访问 localhost ,让我们看看控制台:
至此,我们的第一个 SW 就成功完成了。
SW 的 Debug
在 chrome 中,我们可以通过 chrome://serviceworker-internals/ 查看浏览器注册的所有 SW :
可以在 chrome://inspect/#service-workers 查看到当前正在运行的所有 SW :
点击 inspect ,可以在 Console 中看到 SW 在控制台中输出的内容,在 Source 中,对 SW 进行断点调试
如果你还保留着刚才的那个 localhost ,你可以发现,在 inspect service-worker 页面中,我们刚才的 sw.js 还一直在运行着
Service Worker 的「生命周期」
SW 的生命周期与事件可以在 MDN 上找到,不再重复贴出,SW的缓存与推送通知就是靠着他的生命周期所发出的这些 event 来驱动的。
我想说的是另一个生命周期,我刚才说到,如果我们第一次进入localhost,并一直保留在那个页面,在 inspect service-worker 还可以看到 service worker 还一直运行着
我们可以将 sw.js 改写成如下
setInterval(() => {
console.log('test');
}, 5 * 1000);
在 chrome://serviceworker-internals/ 取消注册 SW ,并强制刷新 localhost( mac下 ⌘+shift+R,windows 下 ctrl+shift+R ),我们会发现控制台中,每五秒就打印了一次 test,这也正是说明,SW在第一次进入的时候没有关闭页面的时候,一直在运行,我们也可以在刚才的 chrome://serviceworker-internals/ 看到这个 SW 正在运行。
然而当我们关闭 localhost 页面,稍等十几秒后,在两个调试页面上, sw.js 都会消失/停止了运行。
我们再直接打开 localhost ,打开控制台,发现只有 Registration succeeded. ,而没有了 test ,再看两个调试页面,发现 sw.js 没有运行。
让我们关闭 localhost 的 tab,将 sw.js 改写如下:
console.log('Yet Another sw.js');
保存后,打开 localhost:
我们刚才对 SW 完成了一次更新,发现他注册 handler 只会注册一次,接下来我们对 SW 的生命周期与运行周期进行探索,看看他在打开网页时,会不会触发生命周期。
我们再对 sw.js 进行重写
self.addEventListener('install', function (event) {
console.log('install');
});
self.addEventListener('activate', function (event) {
console.log('activate');
});
关闭 localhost ,等待 sw 停止运行或者 deactivate 它后再打开 localhost ,控制台显示出了 install 和 activate
再次关闭 localhost ,等待 sw 停止运行再打开 localhost ,控制台上只有 registration succeeded
很明显,SW 的生命周期的事件也只会触发一次,只是相对于他第一次下载后。
更新 SW 的机制可以在MDN上的 #更新你的Service Worker 这章看到,但这仅仅几行文字,而且比较绕口,我认为这其实是SW自身的工作周期,或者说是运行机制
于是在 W3C Working Draft 找到了 Service Worker 的 Lifetime定义
The lifetime of a service worker is tied to the execution lifetime of events and not references held by service worker clients to the ServiceWorker object.
A user agent may terminate service workers at any time it:
* Has no event to handle.
* Detects abnormal operation: such as infinite loops and tasks exceeding imposed time limits (if any) while handling the events.
于是我觉得SW的工作周期结合上边的表现可以由下边的图来表示
最后
SW 是一个事件驱动型 Worker ,本质上,它是个 Worker ,工作在 worker context,所以没有访问 DOM 的权限。如果他要和 window 进行一些操作的话,与 web worker 类似,需要使用 postMessage 这样的方法来传递信息,在window里才能进行DOM操作。
SW 只是个 Event bus,他的几个运行周期的事件,是专门为缓存与推送量身打造的( install 事件和 push 事件),因此他的扩展性也非常好,以后如果有更多需求只需添加事件即可(比如 chrome 实现了 sync 事件是为了弥补离线时从本地到服务器 post 的不好体验),它将我们对资源拉的需求(对服务器的请求资源,对服务器的轮询),隐形转换为了推(本地缓存资源代码控制缓存,统一服务器推送)。
希望这篇文章对 SW 本身的 LifeTime 探究能对你在学习 SW 时有一些帮助