有关安排服务工件注册时间的最佳实践。
服务工件可以显著加快用户对您的 Web 应用的再次访问速度,但您应采取措施确保服务工件的初始安装不会降低用户的首次访问体验。
一般来说,将服务工件注册推迟到初始网页加载完毕后,可为用户提供最佳体验,尤其是网络连接速度较慢的移动设备用户。
常见注册 boilerplate
如果您曾阅读过有关服务工件的文章,可能已经看到过与以下内容大致相似的样板代码:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
这有时可能伴随着一些 console.log()
语句或用于检测之前的 Service Worker 注册更新的代码,以便让用户知道需要刷新页面。但这些只是标准几行代码的细微变化。
navigator.serviceWorker.register
是否有任何细微之处?是否有任何最佳实践可遵循?毫不奇怪(鉴于本文并未到此结束),这两个问题的答案都是“是!”
用户的首次访问
假设用户首次访问某个 Web 应用。此时还没有任何服务工件,并且浏览器无法提前知道最终是否会安装服务工件。
作为开发者,您的首要任务应是确保浏览器快速获取显示交互式网页所需的一组关键资源。任何会减慢检索这些响应速度的因素都是快速互动体验的敌人。
现在假设,在下载网页需要呈现的 JavaScript 或图片的过程中,浏览器决定启动一个后台线程或进程(为简单起见,我们假设它是一个线程)。假设您使用的不是性能强大的桌面设备,而是许多人视为主要设备的低性能手机。启动这个额外的线程会增加对 CPU 时间和内存的争用,而浏览器原本可以将这些资源用于渲染交互式网页。
空闲的后台线程不太可能产生显著差异。但是,如果该线程并非空闲状态,而是决定也要开始从网络下载资源,该怎么办?与许多移动设备可用的带宽有限相比,对 CPU 或内存争用问题的担心应该放在次要位置。带宽非常宝贵,因此请勿同时下载次要资源,以免影响关键资源。
这一切都表明,启动新的服务工件线程以在后台下载和缓存资源,可能会妨碍您实现在用户首次访问您的网站时提供最短互动时间体验的目标。
改进样本代码
解决方法是通过选择何时调用 navigator.serviceWorker.register()
来控制服务工件的启动。一个简单的经验法则是,延迟注册,直到 load
event
在 window
上触发之后,如下所示:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
不过,启动服务工件注册的正确时间还取决于您的 Web 应用在加载后立即执行的操作。例如,2016 年 Google I/O 大会网页应用在转换到主屏幕之前会显示一段动画。我们的团队发现,在动画期间启动服务工注册可能会导致低端移动设备出现卡顿。为了避免给用户带来糟糕的体验,我们推迟了服务工件注册,直到动画播放完毕后(此时浏览器最有可能有几秒钟的空闲时间)。
同样,如果您的 Web 应用使用的是会在网页加载后执行额外设置的框架,请查找用于指示此工作完成时间的框架专用事件。
后续访问
到目前为止,我们一直专注于首次访问体验,但延迟注册服务工件对用户重复访问您的网站有何影响?这可能会让一些人感到意外,但实际上应该不会有任何影响。
注册 Service Worker 后,它会经历 install
和 activate
生命周期事件。Service Worker 激活后,便可处理对您的 Web 应用的任何后续访问的 fetch
事件。Service Worker 会在发出对其作用范围内任何网页的请求之前启动,这在逻辑上是合理的。如果现有服务工件在访问网页之前尚未运行,则无法有机会为导航请求执行 fetch
事件。
因此,一旦有活跃的服务工作器,无论您何时调用 navigator.serviceWorker.register()
,甚至是否调用 navigator.serviceWorker.register()
,都无关紧要。除非您更改服务工件脚本的网址,否则在后续访问期间,navigator.serviceWorker.register()
实际上会无操作。调用时间无关紧要。
提前注册的原因
是否有任何情况下,尽早注册 Service Worker 是有意义的?一个例子是,您的服务工件在首次访问期间使用 clients.claim()
来控制页面,并且服务工件在其 fetch
处理程序内积极执行运行时缓存。在这种情况下,尽快激活服务工件会很有帮助,因为这样可以尝试使用日后可能派上用场的资源填充其运行时缓存。如果您的 Web 应用属于此类别,不妨停下来检查一下,确保您的服务工件的 install
处理程序不会请求与主页面请求争夺带宽的资源。
进行测试
如需模拟首次访问,不妨在 Chrome 无痕式窗口中打开您的 Web 应用,然后在 Chrome 的 DevTools 中查看网络流量。作为 Web 开发者,您可能每天要重载 Web 应用的本地实例数十次。但是,如果您在网站上已经有服务工件且缓存已完全填充的情况下重新访问该网站,您获得的体验与新用户获得的体验并不相同,并且很容易忽略潜在问题。
下面的示例说明了注册时间可能会产生的影响。这两张屏幕截图是在使用无痕模式访问示例应用时拍摄的,其中使用了网络节流来模拟网络连接速度缓慢。

上图反映了修改示例以尽快执行服务工件注册时的网络流量。您可以看到预缓存请求(旁边带有齿轮图标的条目,源自服务工件的 install
处理程序)与显示网页所需的其他资源的请求交错出现。

在上面的屏幕截图中,Service Worker 注册延迟到网页加载完毕之后。您可以看到,只有在从网络提取所有资源后,预缓存请求才会启动,从而消除了对带宽的任何争用。此外,由于我们预缓存的部分内容已位于浏览器的 HTTP 缓存中(“大小”列中显示 (from disk cache)
的内容),因此我们无需再次访问网络,即可填充服务工件的缓存。
如果您是在真实的移动网络上使用实际的低端设备运行此类测试,则会获得额外加分。您可以利用 Chrome 的远程调试功能,通过 USB 将 Android 手机连接到桌面设备,并确保您运行的测试能真实反映许多用户的实际体验。
总结
总而言之,确保用户获得最佳首次访问体验应是首要任务。在初次访问期间,将服务工件注册延迟到网页加载完毕后,有助于确保这一点。您仍然可以获得使用服务工件带来的所有好处,包括提高回访者的体验。
若要确保将服务工件的初始注册延迟到第一个网页加载完毕之后,一种简单的方法是使用以下代码:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}