# 给学习页面加入可控的离线能力

原文: https://yishto.com/learn/application-platforms/pwa-offline-learning/

作者: [Yishto](https://yishto.com/about/)

为学习页面分别设计公共正文、静态资源和私有请求策略，解释Service Worker生命周期、版本缓存与更新恢复。

首次发布: 2026-11-10
内容更新: 2026-11-10

学习页面的离线能力取决于哪些响应被保存、何时更新以及失败时显示什么。加入 manifest 可以描述应用安装信息，却不会自动保存课程。一个可控的首版应只缓存明确公开的学习内容，并让用户知道本地版本。

案例有公开入门页、课程图片和需要身份校验的进度接口。它们共用网站，但数据性质不同。用同一缓存规则处理全部请求，可能将私人响应混进公共缓存，也可能让 HTML 与脚本来自不兼容版本。

## 三种存储各有作用

HTTP 缓存由浏览器和响应头参与管理，Cache API 让程序保存请求与响应，IndexedDB 适合结构化本地数据。它们不是同一层。课程响应可以放 Cache API，用户笔记若需要持久编辑，应有独立数据模型，不能靠缓存页面完成。

先列资源类型。带版本指纹的公共脚本适合复用已缓存副本；正文可以优先请求网络，再在失败时使用已保存版本；私人进度接口保持身份校验并采用独立离线队列设计。这里不把含认证内容的响应加入公共离线缓存。

缓存内容也会受到配额和浏览器存储处理影响。可离线标签应依据本地可读取内容，不能承诺所有保存文件永久存在。用户清理浏览器数据后，应用要回到明确空状态，提供重新下载。

## Service Worker 的生命周期

Service Worker 在独立 worker 环境运行，没有 DOM。部署通常需要 HTTPS，浏览器将 localhost 用作本地开发例外。注册路径与 scope 决定可控制的范围，放在深层目录的脚本不能随意接管整站。

首次注册经历安装和激活，安装时可以准备小规模必需资源。已有页面不一定立即受到新 worker 控制，需要检查 controller 或重新加载。worker 可能停止，内存全局变量不能作为长期下载记录。

更新时旧 worker 可以继续控制已打开页面，新 worker 安装后等待。若直接强制切换，旧 HTML 可能使用新脚本或已删除资源。更新策略应让页面和资源版本保持一致，并在用户安全保存输入后提示刷新。

## 在工程中加入离线层

先让普通学习站点在线可用，准备两篇公开正文和一张教学图片。通过 localhost 服务运行，建立 manifest 并在页面关联它；图标和安装体验按目标浏览器要求核对。安装与离线分别验收，不能只看到安装入口就宣布成功。

创建 worker 文件，从页面检查能力后注册。划定 scope，再实现安装处理，预缓存少量确定存在的资源。使用事件的完成管理机制让浏览器知道安装工作仍在进行；某个必要资源失败时不要把不完整缓存当作已准备好的版本。

随后增加 fetch 路由：仅处理约定方法、同源公开路径与可缓存响应，其他请求按原路网络发送。读取缓存时提供明确的离线缺失页面，避免返回一段看似成功的空正文。开发者工具中检查注册、控制页面和缓存内容，再做浏览器重启测试。

## 网络优先策略的离线模型

下面 Python 示例在有有效网络结果时更新缓存，网络不可用时读副本，没有副本时显示缺失。它仅是策略模型，没有 Service Worker、浏览器 Response、HTTP 状态和缓存生命周期。

```python
cache = {"/learn/intro": "saved article"}
def read(path, online, response=None):
    if online and response is not None:
        cache[path] = response
        return response
    return cache.get(path, "offline unavailable")
assert read("/learn/intro", False) == "saved article"
assert read("/learn/new", False) == "offline unavailable"
assert read("/learn/intro", True, "updated") == "updated"
print(read("/learn/intro", False))
```

实际输出 `updated`。初次断网读到 `saved article`，未保存路径得到 `offline unavailable`，一次网络更新后断网读取新内容。真实 fetch 实现要判断响应是否成功、路径是否公开以及是否符合缓存条件，不能把任意失败响应存进去。

网络优先需要设定可接受的等待和失败处理。单纯依赖“在线”标志不可靠，设备连上网络仍可能无法访问服务。以实际请求结果决定回退，同时将是否显示旧版本反馈给页面。

## 版本更新与清理顺序

缓存名包含应用版本，安装新版本时写入新命名空间，激活时按明确规则清理不再需要的旧资源。清理只能覆盖应用拥有的缓存名称，不能遍历删除其他用途的数据，更不能把用户笔记当旧资源删掉。

对正文分别保存内容版本或更新时间来源，让“已保存版本”可以解释。更新失败时继续使用已经完整可读的版本，不提前删旧内容。页面显示更新提示，用户确认且输入保存后才进入刷新流程。

回退策略需要与资源保留规则配合。旧页面仍在运行时，删除它引用的版本文件可能造成加载失败。测试多标签页：一个保持旧页面，另一个获取新版本，观察 worker 等待与激活，确认两边都不陷入部分更新状态。

## 私人请求与错误边界

进度接口每次仍要验证身份，客户端缓存规则不构成授权。注销时清理与用户关联的本地状态，避免下一个使用者看到前一个人的数据。不要把令牌写进可分享 URL 或公共缓存键。

公共内容也要限制缓存规模。大型资料下载提供用户主动操作、进度和失败状态；安装预缓存不应塞入整个资料库。资源不存在、空间不足与网络失败分别提示恢复动作，防止无限重试占满容量。

开发时还需避免缓存隐藏修改。每轮测试记录当前worker版本和controller，必要时使用开发工具的更新或注销操作，但最终要重放正常用户更新路径。仅在开发工具强制刷新后成功，不能证明已打开页面自然切换也正确。

缓存路由要将响应与请求约定对应，例如只对GET正文读取复用副本。含查询参数的URL可能表示不同内容，规范化之前先说明参数语义，不能简单删除所有参数。响应的缓存与隐私限制也应遵守；遇到无法缓存的结果就按网络或缺失路径处理，而不是强行转换后放进公共缓存。

## 练习：完整验证四种访问

用浏览器开发工具测试首次访问就断网、在线访问后断网、正文更新后断网、旧标签页等待更新。首次无缓存时显示明确缺失，已保存版本可读，更新失败仍保留完整旧内容。

检查缓存中只出现允许的公共资源，私人进度响应不在其中。清理应用资源缓存后，用户结构化笔记不受影响。最后在目标浏览器与设备测试安装和重开，记录差异；Python 输出只能验证示例策略，不能证明安装性或实际 worker 控制行为。

## 资料来源与延伸阅读

- [Progressive web applications](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
- [Using service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [多语言页面怎样兼顾 SEO 和加载体验](https://yishto.com/learn/web-interface/localization-and-web-performance/)
- [从页面结构到可操作的界面](https://yishto.com/learn/web-interface/html-layout-accessibility/)
