# Service worker 注册与 scope

> navigator.serviceWorker.register() 的工作原理、scope 的控制范围，以及如何与 URL 结构对齐。

**一句话：** `navigator.serviceWorker.register(scriptURL, { scope })` 注册一个 service worker，它会拦截路径以 scope 开头的所有 URL 的 `fetch` 事件。scope 默认为 worker 脚本所在目录，只能收窄、不能扩展到该范围之外——除非服务器返回 `Service-Worker-Allowed` 响应头。

## 注册如何工作

调用 `register()` 是幂等的——若相同脚本已在该 scope 注册，浏览器会忽略此次调用。无论是否触发了新的安装，返回的 Promise 都会 resolve 为一个 `ServiceWorkerRegistration` 对象。

```js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/sw.js', { scope: '/' })
    .then((registration) => {
      console.log('注册成功。Scope:', registration.scope);
    })
    .catch((err) => {
      console.error('注册失败:', err);
    });
}
```

注册应在页面主要内容加载完成后进行，而非放在 `<head>` 顶部，以免与关键资源抢占带宽。

## Scope 控制什么

scope 是一个 URL 路径前缀。注册了 scope `/app/` 的 service worker 会拦截：

- `/app/`
- `/app/dashboard`
- `/app/settings/profile`

但不会拦截：

- `/`（根路径）
- `/blog/`
- `/app-launcher/`（不同前缀）

scope 不限制 worker 通过 `fetch()` *发起*请求的目标 URL，只限制哪些页面导航和子资源请求会被 *提供*给该 worker 处理。

## 默认 Scope

若未指定 `scope` 选项，默认为 worker 脚本所在目录：

- Worker 位于 `/sw.js` → 默认 scope `/`
- Worker 位于 `/app/sw.js` → 默认 scope `/app/`

将 worker 放在根路径（`/sw.js`）可获得最大默认 scope。

## 通过 `Service-Worker-Allowed` 扩展 Scope

`/app/sw.js` 通常无法声明 `/` 的 scope——其范围受自身路径约束。若需要更大的 scope，服务器必须在浏览器拉取 worker 脚本时附上 `Service-Worker-Allowed` 响应头：

```
Service-Worker-Allowed: /
```

若没有此响应头，以 `{ scope: '/' }` 注册 `/app/sw.js` 会抛出 `DOMException`。

## 多重注册

同一来源可以有多个注册，允许为不同子应用部署不同的 worker：

```js
// Worker A 覆盖营销页面
navigator.serviceWorker.register('/sw-marketing.js', { scope: '/marketing/' });

// Worker B 覆盖应用外壳
navigator.serviceWorker.register('/sw-app.js', { scope: '/app/' });
```

Scope 之间可以重叠——浏览器在判断由哪个 worker 处理请求时，会匹配最具体（最长）的 scope。保持 scope 不重叠是运维层面的最佳实践，而非注册约束。

## `updateViaCache`

默认情况下，浏览器在获取 worker 脚本时会遵循其 HTTP 缓存，若脚本被积极缓存可能延迟更新。设置 `updateViaCache: 'none'` 可让浏览器始终绕过 worker 脚本本身的 HTTP 缓存：

```js
navigator.serviceWorker.register('/sw.js', {
  scope: '/',
  updateViaCache: 'none',
});
```

W3C Service Workers 规范将 `updateViaCache` 定义为控制 HTTP 缓存是否被用于 worker 脚本（`'all'`）、其导入脚本（`'imports'`，默认值）或二者均不使用（`'none'`）。

## 实践清单

- [ ] 在 `DOMContentLoaded` 或 `load` 事件后注册，避免与资源加载竞争。
- [ ] 将 worker 脚本放在所需 scope 路径层级尽可能高的位置（`/sw.js` 对应 `/`，`/app/sw.js` 对应 `/app/`）。
- [ ] 注册时使用 `updateViaCache: 'none'`，确保浏览器始终检查新版本。
- [ ] 在开发环境中打印 `registration.scope`，确认 worker 覆盖了预期路径。
- [ ] 多应用站点应定义不重叠的 scope，并为每个 scope 部署独立的 worker。

## 相关参考

- [Service Worker 生命周期](/zh/reference/service-worker/lifecycle/) — 注册之后发生的事（install、activate、waiting）
- [更新流程与 skipWaiting](/zh/reference/service-worker/update-skipwaiting/) — 新版本如何替换旧版本
- [Manifest scope](/zh/reference/manifest/scope/) — Web App Manifest 中的类似概念