`self.Clients.claim()`的作用是什么?

28
注册服务工作者,我可以调用:

navigator.serviceWorker.register()

navigator.serviceWorker.register('/worker.js')

每次页面加载时,它都会检查worker.js的更新版本。如果找到更新,则在关闭并重新打开页面的所有选项卡之前,新的工作程序将不会被使用。我所阅读的解决方案是:

self.addEventListener('install', function(event) {
  event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
  event.waitUntil(self.clients.claim());
});
我能理解 skipWaiting 的部分,但是 clients.claim() 到底是做什么的?我进行了一些简单的测试,似乎即使没有它也能正常工作。

3
快速评论:调用registration.update()并不是为了检查是否有更新的服务工作者脚本而必需的。在导航请求后,浏览器已经自动完成了此操作。registration.update()允许您在不必等待导航请求的情况下进行检查,但通常是不必要的。 - Jeff Posnick
是的,我刚写完问题就发现了这一点。 - BonsaiOak
4个回答

19
我摘自服务工作器生命周期指南的以下内容:

clients.claim

在你的服务工作者激活后,调用clients.claim()可以控制未受控制的客户端。

这里是一个以上述方法为基础并在其激活事件中调用clients.claim()的演示版本。第一次您应该看到猫。我说“应该”,因为这取决于时间敏感性。只有在服务工作者激活且clients.claim()生效之前图像才会加载,否则就看不到猫。

如果您使用服务工作者以不同于通过网络加载页面的方式进行页面加载,则clients.claim()可能会带来麻烦,因为您的服务工作者最终会控制某些没有它的客户端。

注意:我发现很多人将clients.claim()包含在样板中,但我自己很少这样做。它只在第一次加载时真正重要,并且由于渐进增强,页面通常可以在没有服务工作者的情况下正常工作。


5
我理解 self.skipWaiting() 会立即应用更新到已有的 serviceWorker,而 clients.claim() 则用于在第一次加载时立即接管控制。 - BonsaiOak

16

我也曾经难以理解Clients.claim的作用,看过很多解释都不够清晰明了。希望我的回答能够帮助到跟我一样困惑的人。

要理解Clients.claim,我们需要了解service worker的生命周期:

  1. 安装阶段:注册后的第一阶段。当oninstall处理程序完成时,服务 worker 被视为已安装。
  2. 已安装阶段:等待使用其他服务 worker 的客户端。
  3. 激活阶段:没有被其他服务 worker 控制的客户端。当onactivate处理程序完成时,服务 worker 被视为已激活。
  4. 已激活阶段:服务 worker 现在控制着页面。

skipWaitingClients.claim 解决的是不同的问题。

Clients.claim 仅对于网页从未被 service worker 控制变为被控制的情况有影响

skipWaiting 正如它所说的那样,它跳过等待阶段并直接进入激活状态。一旦激活,它就是所有客户端的活动服务 worker,其中客户端是指在您的 service worker 范围内打开网页的任何窗口或选项卡。


那么我们为什么还需要Clients.claim呢?

这也曾经让我困惑,可能你也是。最好通过一个例子来说明回答。

假设您的网页尚未注册服务 worker,因此未被控制。您有两个标签页(客户端)打开了您的网页。您更新网页以便注册服务 worker。

你决定重新加载第一个选项卡(客户端),它现在会获取注册服务 worker 的脚本并开始安装。安装完成后,它会注意到没有其他客户端正在被服务 worker 控制,因此不必等待,可以立即安全地激活服务 worker,并且任何资源的获取现在都将通过您的服务 worker 进行。

然而,这里有个问题,另一个标签页(客户端)现在也有了一个活动的服务 worker,但是它还没有被控制。这意味着任何获取都不会通过服务 worker 进行。您需要重新加载任何其他标签页(客户端)才能使其被活动服务 worker 控制。这很令人困惑,因为如果以后任何新的服务 worker 通过强制使用skipWaiting而变为活动状态,则其他标签页(客户端)将立即由新的活动服务 worker 控制。因此我强调重新加载部分仅在未被控制的网页变为受控制时需要。

这时就需要Clients.claim了。当您在第一个服务 worker 激活时调用 self.clients.claim(),像这样:

self.addEventListener('activate', event => {
  event.waitUntil(clients.claim());
});

它将确保其他未受控制但具有活动服务工作者的选项卡(客户端)由活动服务工作者控制。这意味着对于任何资源的提取现在都会通过活动服务工作者进行。如果没有Clients.claim,则服务工作者直到重新加载页面才被使用。

同样,如果所有网页已由服务工作者控制。 如果检测到并安装了一个新的服务工作者,则通常等待关闭包含该网页(客户端)的所有选项卡。下次访问该网页时,将激活新的服务工作者并由其控制该网页。

但是,如果您不关闭所有客户端,并使用skipWaiting强制使用新的服务工作者,它将立即对所有客户端进行激活和控制。这意味着来自任何客户端的任何新提取都将立即通过您的新服务工作者进行。现在,您无需使用Clients.claim即可使其他客户端开始使用新的服务工作者。

这是我的尝试,希望能帮助到某些人。


这确实对我有帮助。但是,如之前所解释的,我们应该避免使用 skipWaitingclients.claim 吗?让新的服务工作者控制旧页面可能会引起麻烦,不是吗? - Lewis
这真的取决于情况和你的代码在做什么。在大多数情况下,它不会引起任何麻烦,这就是为什么它们首先存在并且最常用的原因。然而,只有作为开发人员的您才能理解推送(新)服务工作者时的影响,例如旧的数据库结构。 - Mark
我认为有一个小错误,当重新加载第一个标签并首次注册服务工作者后,在变为活动状态之后,您需要再次重新加载标签,然后任何资源的获取都将通过您的服务工作者进行,就像您提到的其他标签一样。 - Paul Wang

12

Service worker会在注册后控制下一个页面重新加载。通过使用self.skipWaiting()self.clients.claim(),可以要求客户端在首次加载时就接管服务工作线程。

例如:

假设我缓存了一个名为hello.txt的文件,并且再次调用hello.txt,即使我的缓存中有资源,它也需要向服务器发出请求。这是我不使用self.clients.claim()的情况。但是在下一页重新加载时,对于hello.txt的服务器调用将从缓存中提供资源。

为了解决这个问题,我必须使用self.skipWaiting()self.clients.claim()的组合,以便服务工作线程一旦激活就开始提供内容。

P.S:

next page-reload指页面重新访问。

first load表示第一次访问页面的时刻。


那么使用 self.clients.claim 会调用服务器获取文件,即使该文件已经在缓存中了?是这样吗?您能否清楚地说明一下“下一页重新加载”或“第一次加载”的含义? - xkeshav
1
使用 self.clients.claim 不会调用服务器,如果文件在缓存中,则会从缓存中提供服务。根据您的建议,已编辑帖子,涉及“下一页重新加载”和“第一次加载”。 - Imamudin Naseem
谢谢您的修改,但我仍然不清楚在哪种情况下会使用self.clients.claim(),以及在哪种情况下不需要编写此方法?如果我想始终从服务器获取文件内容,那么我需要编写self.clients.claim()吗?之后它将从缓存中提取。 - xkeshav
但是如果我需要一个文件始终从服务器获取,而其他文件可以从缓存中获取(如果存在),因为我们在“activate”事件侦听器中编写了self.client.claims并且只能使用一个设置进行配置。要么写它要么不写。 - xkeshav
您可以随时配置要缓存的资产清单。对于其他资产,服务工作者将自动绕过向服务器发出请求的操作。 - Imamudin Naseem
显示剩余2条评论

3

Clients.claim() 使服务工作者在您首次注册服务工作者时控制页面。如果页面上已经有一个服务工作者,它将不会产生任何影响。skipWaiting() 使新的服务工作者替换旧的服务工作者。没有它,您必须关闭页面(以及在相同范围内包含页面的任何其他打开的选项卡),然后才能激活新的服务工作者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接