# Clients API：service worker 对客户端对象的访问能力

> service worker 内通过 self.clients 暴露的 Clients 接口如何提供 get()、matchAll()、openWindow()、claim() 方法，用于查找、聚焦、发消息给窗口和 worker 客户端，或接管控制权，以及其 Baseline 广泛可用的浏览器支持情况。

**一句话：** `Clients` 接口在 service worker 内部通过 `self.clients` 访问，提供对符合查询选项的 `Client` 对象（窗口、worker）的访问——通过 `matchAll({ includeUncontrolled: true })`，还可以包含与该 service worker 共享同一 storage key/同源、但未被其控制、也未与其注册关联的客户端。

该特性仅在 service worker 中可用。

## 访问方式

```js
// 在 service worker 内部
self.clients;
```

## 方法

| 方法 | 返回值 | 作用 |
|---|---|---|
| `Clients.get(id)` | `Promise<Client \| undefined>` | 返回与给定 `id` 匹配的 `Client`；若没有匹配的客户端则解析为 `undefined`。 |
| `Clients.matchAll(options)` | `Promise<Array<Client>>` | 返回一个 `Client` 对象数组；默认只返回受控客户端，options 参数可控制返回哪些类型的客户端，包括尚未受控的客户端。 |
| `Clients.openWindow(url)` | `Promise<WindowClient \| null>` | 为给定的 URL 打开一个新的浏览器窗口；若平台无法为其返回同源的 `WindowClient`，则解析为 `null`。 |
| `Clients.claim()` | 一个解析为 `undefined` 的 `Promise` | 让处于激活状态的 service worker 将自己设置为其作用域内所有客户端的控制者。 |

## 示例

查找已打开的聊天窗口并聚焦，或在未找到时打开一个新窗口，然后向其发送消息：

```js
addEventListener("notificationclick", (event) => {
  event.waitUntil(
    (async () => {
      const allClients = await clients.matchAll({
        includeUncontrolled: true,
      });

      let chatClient;

      // 看看是否已经有一个聊天窗口打开：
      for (const client of allClients) {
        const url = new URL(client.url);

        if (url.pathname === "/chat/") {
          // 太好了，用它！
          client.focus();
          chatClient = client;
          break;
        }
      }

      // 如果没有找到已有的聊天窗口，
      // 打开一个新的：
      chatClient ??= await clients.openWindow("/chat/");

      // 向该客户端发送消息：
      chatClient.postMessage("New chat messages!");
    })(),
  );
});
```

## 浏览器支持

`Clients` 接口属于 Baseline 广泛可用：它已经在众多设备和浏览器版本中得到充分确立并可正常使用，自 2018 年 4 月起已在各浏览器中可用。

## 实用清单

- [ ] 当 service worker 需要查找它尚未控制的窗口时，使用带 `includeUncontrolled` 选项的 `clients.matchAll()`——默认情况下 `matchAll()` 只返回受控客户端。
- [ ] 处理 `clients.get(id)` 解析为 `undefined`、`clients.openWindow(url)` 解析为 `null` 的情况（即找不到匹配客户端时）。
- [ ] 如果 service worker 需要立即接管现有客户端，而不是等到下一次导航，在 `activate` 事件处理函数中调用 `clients.claim()`。
- [ ] 仅在用户手势（例如点击通知）的响应中使用 `clients.openWindow()`，遵循上面示例展示的模式。

## 相关参考

- [Navigation preload](/zh/reference/service-worker/navigation-preload/) — 另一个通过类似作用域 API 暴露的 service worker 能力