HTML5 的离线存储主要通过 Application Cache (AppCache) 和 Service Worker 两种技术实现。需要注意的是,Application Cache
已被现代浏览器废弃,因其存在诸多设计缺陷,而 Service Worker
是当前推荐的、更强大和灵活的离线解决方案。
下面分别介绍这两种技术的管理与加载机制,并重点阐述现代标准 Service Worker。
1. Application Cache (AppCache) – (已废弃,了解即可)
AppCache 使用一个 manifest 文件(.appcache
或 .manifest
)来告诉浏览器哪些资源需要缓存。
管理和加载流程:
- 首次访问:
- 浏览器解析 HTML,发现
<html manifest="cache.appcache">
标签。 - 浏览器请求并下载
cache.appcache
文件。 - 解析 manifest 文件,下载其中列出的所有资源(HTML, CSS, JS, 图片等),并将其存储在浏览器的专用缓存中。
- 页面正常加载并显示。
- 浏览器解析 HTML,发现
- 后续访问(在线):
- 浏览器检查 manifest 文件是否有更新(通过文件内容变化,而非时间戳)。
- 如果有更新:浏览器重新下载所有在 manifest 中列出的资源(即使只有一个文件变了,也会全部重新下载),更新缓存,然后使用新缓存加载页面。
- 如果没有更新:直接从本地缓存加载所有资源,页面快速显示。
- 离线访问:
- 浏览器检测到网络不可用。
- 自动从本地缓存中加载 manifest 文件中列出的所有资源。
- 页面正常显示。
AppCache 的致命缺点(导致其被废弃):
- 全量更新:manifest 文件中任何一处修改,都会导致所有资源重新下载,效率低下。
- 首次加载后才缓存:用户第一次访问必须在线,无法立即离线使用。
- 更新机制复杂且不透明:开发者难以精确控制更新时机。
- 缓存清除困难:一旦缓存,很难清除,容易导致用户看到旧版本。
- 安全性问题:存在潜在的安全风险。
结论:AppCache 已被 W3C 标准移除,不应在新项目中使用。
2. Service Worker – (现代标准,推荐)
Service Worker 是一个运行在浏览器后台的 JavaScript 脚本,独立于网页主线程。它充当网络请求的代理,可以拦截、处理和响应网络请求,从而实现强大的离线功能。
管理和加载流程:
- 注册 (Registration):
- 在主页面的 JavaScript 中(通常在
window.onload
或DOMContentLoaded
事件后),调用navigator.serviceWorker.register('/sw.js')
。 - 浏览器下载
sw.js
文件,并在后台启动 Service Worker。
- 在主页面的 JavaScript 中(通常在
- 安装 (Install):
- Service Worker 首次启动时,会触发
install
事件。 - 开发者在
install
事件的监听器中,使用 Cache API 将关键资源(如首页 HTML、核心 CSS、JS、离线页面)预先缓存到浏览器的Cache Storage
中。
self.addEventListener('install', (event) => { event.waitUntil( caches.open('v1').then((cache) => { return cache.addAll([ '/', '/styles/main.css', '/scripts/app.js', '/offline.html' ]); }) ); });
- Service Worker 首次启动时,会触发
- 激活 (Activate):
- 安装成功后,Service Worker 进入
activating
状态,然后触发activate
事件。 - 在
activate
事件中,通常进行缓存清理,删除旧版本的缓存,避免占用过多空间。
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.filter((cacheName) => { return cacheName.startsWith('my-app-') && cacheName !== 'v1'; }).map((cacheName) => { return caches.delete(cacheName); }) ); }) ); });
- 安装成功后,Service Worker 进入
- 网络拦截与资源加载 (Fetch):
- 激活后,Service Worker 开始拦截页面发出的所有网络请求(通过
fetch
事件)。 - 开发者可以在此事件中编写自定义逻辑来决定资源从哪里加载:
- 优先缓存:先检查
Cache Storage
中是否有请求的资源,如果有,直接返回缓存版本(最快)。 - 网络回退:如果缓存中没有,则发起网络请求,获取资源后,可以将其存入缓存(供下次使用),然后返回给页面。
- 离线策略:如果网络请求失败(离线),可以返回一个预缓存的“离线页面”(
offline.html
)或缓存中的降级资源。
- 优先缓存:先检查
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { // 如果缓存中有,返回缓存 if (response) { return response; } // 否则,发起网络请求 return fetch(event.request).then( (networkResponse) => { // 将网络响应克隆并存入缓存(可选) const responseToCache = networkResponse.clone(); caches.open('v1').then((cache) => { cache.put(event.request, responseToCache); }); return networkResponse; } ).catch(() => { // 网络请求失败,返回离线页面 return caches.match('/offline.html'); }); }) ); });
- 激活后,Service Worker 开始拦截页面发出的所有网络请求(通过
- 更新:
- 当开发者更新了
sw.js
文件,浏览器会检测到文件内容变化(字节不同)。 - 新的 Service Worker 会启动安装过程,但不会立即激活。
- 当所有使用旧 Service Worker 的页面都被关闭后,新的 Service Worker 才会激活,接管网络请求。
- 开发者可以在
install
和activate
事件中精确控制新旧缓存的迁移和清理。
- 当开发者更新了
Service Worker 的优势:
- 精细控制:开发者可以完全控制缓存策略(缓存哪些、何时缓存、如何更新)。
- 增量更新:可以只更新变化的资源,无需全量下载。
- 离线优先:可以实现“离线优先”或“网络优先”等多种策略。
- 后台同步:支持
Background Sync
,在网络恢复后自动同步数据。 - 推送通知:支持
Push API
,实现消息推送。 - 性能更好:避免了 AppCache 的许多性能陷阱。
总结
特性 | Application Cache (AppCache) | Service Worker |
---|---|---|
状态 | 已废弃 | 现代标准,推荐使用 |
核心机制 | Manifest 文件列表 | JavaScript 脚本代理网络请求 |
缓存管理 | 自动、全量、不灵活 | 手动、增量、高度灵活 |
更新机制 | 全量更新,不透明 | 增量更新,开发者可控 |
离线加载 | 直接从缓存加载 manifest 列表中的资源 | 通过 fetch 事件拦截,按策略返回缓存或网络资源 |
首次加载 | 必须在线 | 可以设计为离线可用 |
功能 | 仅限离线缓存 | 离线、后台同步、消息推送、拦截路由等 |
结论:浏览器通过 Service Worker 对 HTML5 的离线资源进行管理。它通过注册、安装、激活三个生命周期阶段来安装和更新缓存,并通过拦截 fetch
事件来智能地决定资源是从本地缓存还是网络加载,从而实现强大、灵活且可靠的离线体验。开发者应使用 Service Worker 而非已废弃的 AppCache。
THE END