# 更新策略

> 选择新版本 service worker 如何推送给用户——立即接管、用户确认后再刷新，或安静的默认行为——以及何时需要周期性更新检查。

**目标：** 让新版本 service worker 触达用户的方式是一个经过权衡的选择，而不是意外落地的
默认行为。具体机制（`skipWaiting()`、`clients.claim()`、waiting 状态）见
[更新流程参考](/zh/reference/service-worker/update-skipwaiting/)；本指南讨论的是该在
这几种策略中如何取舍。

## 默认行为：一致性

如果不调用 `skipWaiting()`，新安装的 worker 会停留在 *waiting* 状态，直到所有由旧 worker
控制的标签页都关闭。默认行为偏向一致性：一个没有 service worker 的页面不会在会话中途
突然被接管；一个在某个 worker 版本下加载的页面，也不会被交给缓存假设不同的另一个版本。
对大多数应用而言，这种安静的默认行为就是正确的选择——但仅仅刷新并不能保证更新生效：
刷新期间，即使只有一个标签页，新旧页面的 client 也可能短暂重叠，因此只有当所有由旧
worker 控制的标签页都真正关闭或已导航离开时，等待中的 worker 才会接管。

## 用 `skipWaiting()` 立即接管

在 `install` 事件中调用 `self.skipWaiting()`，会让新 worker 在进入 waiting 阶段后立即
踢掉当前活动的 worker 并自行激活，即便它仍在控制着已打开的标签页。直接在 install
处理函数中调用是常见做法：

```js
self.addEventListener('install', (event) => {
  self.skipWaiting();
});
```

代价是：这样一来，你的新 service worker 很可能正在控制着用旧版本加载的页面——如果新
worker 缓存的资源与页面上已有的 HTML 不兼容，就可能出问题。指导原则很直接——如果这可能
带来破坏，就不要无条件使用 `skipWaiting()`。

`clients.claim()` 是一个相关但独立的决定：它让新激活的 worker 接管那些在其注册时就已经
打开的页面。它只在首次加载时才真正有意义，因为得益于渐进增强，页面在没有 service worker
时通常也能正常工作——所以应把它当作按情况使用的选项，而非常规样板代码。

## 用户确认后再刷新

一种折中方案是把 `skipWaiting()` 推迟到由用户驱动的时刻，而不是在 `install` 中无条件
调用：新 worker 安装并等待，页面向用户展示"有可用更新"的提示，只有在用户操作之后，页面
才会通过 `postMessage()` 通知等待中的 worker 触发 `skipWaiting()`，随后进行一次可控的
刷新。

```js
// 在页面中：监听 controller 变化，并在用户同意更新之后只刷新一次。
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (refreshing) return;
  refreshing = true;
  window.location.reload();
});
```

这种模式——完整说明见[更新流程参考](/zh/reference/service-worker/update-skipwaiting/)——
既避免了无条件 `skipWaiting()` 带来的隐性不一致风险，也避免了纯默认行为「更新要等所有
标签页关闭才生效」的代价。

## 周期性更新检查

浏览器会在导航到域内页面时自动检查是否有新的 worker 脚本，也会在诸如 `push` 与 `sync`
之类的功能性事件发生时检查（除非此前 24 小时内已经检查过）。如果预计用户会长时间保持
某个标签页打开、既不导航也不刷新，你可能需要按一定间隔（例如每小时）主动调用
`registration.update()`，让长时间存活的会话也能在合理时间内发现新版本：

```js
navigator.serviceWorker.register('/sw.js').then((reg) => {
  setInterval(() => reg.update(), 60 * 60 * 1000);
});
```

## 如何选择策略

| 场景 | 建议策略 |
|---|---|
| 常规应用；用户最终会关闭或导航离开所有标签页 | 默认行为（不调用 `skipWaiting()`）——最简单、最一致。 |
| 用户会长时间保持标签页打开（数小时/数天） | 默认行为 + 周期性 `registration.update()` 检查，即使没有新的导航也能发现更新。 |
| 更新带有破坏性的缓存/schema 变化 | 默认行为，或用户确认后再刷新——绝不无条件使用 `skipWaiting()`，因为它可能接管带有不兼容假设的旧页面。 |
| 非破坏性修复，希望尽快生效，且可以接受一定干扰 | 在 `install` 中无条件调用 `skipWaiting()`。 |

## 下一步

- [Service worker 更新流程与 skipWaiting](/zh/reference/service-worker/update-skipwaiting/) —— 本指南在其间取舍的底层机制。
- [离线策略](/zh/guides/offline/) —— 缓存策略与更新策略在感知新鲜度上有所重叠。
- [Service worker 生命周期](/zh/reference/service-worker/lifecycle/) —— 完整的 install/activate/waiting 流程。

← 返回[指南](/zh/guides/)总览。