HTML5 离线存储怎么用?工作原理如何?
面试速答(30 秒版 TL;DR)
- “离线存储”常见两层含义:
- 离线缓存资源(让页面/静态资源离线可用):现代主流是 Service Worker + Cache API(PWA 方案)。
- 离线存数据(把业务数据留在本地):常用 IndexedDB,其次是
localStorage/sessionStorage(更适合小体量、低频配置)。
- 面试里说的“HTML5 离线存储”很多是历史题:早期方案 Application Cache(AppCache) 通过
manifest文件声明要缓存的资源,但它已废弃,新项目不建议用。 - 工作原理一句话:
- AppCache:浏览器根据
manifest生成“应用缓存”,离线优先读缓存;更新依赖manifest变化,且通常要 刷新/重开页面 才生效。 - Service Worker:浏览器在后台线程运行 SW,拦截
fetch,你用代码决定“走网络还是走缓存”,并通过版本化缓存实现可控更新。
- AppCache:浏览器根据
心智模型:离线“资源” vs 离线“数据”
| 目标 | 典型技术 | 本质 | 常见坑 |
|---|---|---|---|
| 让页面资源离线可用 | Service Worker + Cache API(推荐) | 缓存 Request/Response | 更新策略、缓存失效、离线兜底页 |
| 让页面资源离线可用(历史) | AppCache(废弃) | 由 manifest 声明的资源集合 | 更新不直观、缓存不受控、任一资源失败导致整次更新失败 |
| 让业务数据离线可用 | IndexedDB(推荐) | 浏览器端数据库 | 需要封装、版本迁移、事务与索引设计 |
| 轻量持久化 | localStorage | 同步 KV(字符串) | 阻塞主线程、容量小、XSS 风险高(别存敏感信息) |
下面重点讲“离线缓存资源”的两套方案:AppCache(历史题) 与 Service Worker(现代题)。
方案一(历史题):Application Cache(AppCache)
注意
AppCache 已被废弃,现代浏览器对它的支持逐步移除,新项目不要用。面试提到它,通常是考你是否理解它的 用法、缓存规则、更新机制与坑。
1)怎么用(基本步骤)
- 写一个 manifest 文件(扩展名不重要,内容格式重要)
- 在 HTML 根元素上声明
manifest:<html manifest="/app.appcache"> - 服务端正确返回 manifest:
- 让浏览器每次都能检查更新:通常会给 manifest 配
Cache-Control: no-cache(避免 manifest 自己被强缓存导致永远不更新) - 任何被缓存的资源如果 404/超时/下载失败,可能导致本次更新整体失败
- 让浏览器每次都能检查更新:通常会给 manifest 配
一个最小示例:
<!doctype html>
<html manifest="/app.appcache">
<head>
<meta charset="utf-8" />
<title>AppCache Demo</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<script src="/app.js"></script>
</body>
</html>
/app.appcache(示例):
CACHE MANIFEST
# v1.0.0 (改这行就能触发更新检查的“内容变化”)
CACHE:
/styles.css
/app.js
/logo.png
NETWORK:
*
FALLBACK:
/ /offline.html
2)manifest 的三段(面试高频)
CACHE::显式缓存的资源列表(离线时主要靠它)NETWORK::必须走网络的资源- 写
*表示“除 manifest 指定的缓存资源外,其它请求都走网络”
- 写
FALLBACK::网络不可用时的降级映射(常用于离线兜底页)
3)工作原理与更新机制(核心考点)
AppCache 有一个非常反直觉的点:“检查更新”与“使用新缓存”不是同一件事。
你需要记住的结论:
- 更新触发条件:浏览器认为 manifest 内容有变化(所以很多人会在 manifest 里放
# version注释行做“版本戳”)。 - 更新时机:下载新资源集一般在后台进行;更新完成会进入“ready”状态。
- 生效时机:多数情况下要 刷新/重开页面(或显式
swapCache())才切到新缓存。 - 一致性策略:AppCache 倾向于“整包更新”(某个资源失败可能导致整次更新失败),这也是它难用的原因之一。
4)典型追问与坑
- 为什么 AppCache 容易“发布了用户还在旧版本”?
- 因为资源默认离线优先,且“新缓存就绪”不等于“立刻生效”,用户不刷新就可能继续用旧缓存。
- 为什么改了 JS/CSS 但没更新?
- 很多时候是 manifest 被强缓存 了,浏览器根本没去重新拉取 manifest,自然也不会触发更新。
- 为什么一加离线缓存反而更不稳定?
- AppCache 对错误非常敏感:manifest 语法错误、资源下载失败等,都会导致缓存更新流程不可控。
方案二(推荐):Service Worker + Cache API(PWA)
1)怎么用(最小可用)
页面注册:
// main.ts
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
/sw.js(极简 cache-first 示例):
const CACHE_NAME = 'app-cache-v1';
const PRECACHE_URLS = ['/', '/index.html', '/styles.css', '/app.js', '/offline.html'];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_URLS);
}),
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key)),
);
}),
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).catch(() => caches.match('/offline.html'));
}),
);
});
2)工作原理(抓住生命周期 + 拦截点)
面试可背的结论:
- SW 是可编程的代理层:你可以实现 cache-first、network-first、stale-while-revalidate 等策略。
- 更新可控:常用做法是缓存名版本化(如
app-cache-v2),activate时清理旧缓存。 - 离线兜底:网络失败时返回
/offline.html或离线骨架页。
速记要点(可背诵)
- “HTML5 离线存储”如果指资源离线:AppCache(历史) vs Service Worker + Cache API(现代)。
- AppCache 用法:
<html manifest>+CACHE MANIFEST文件(CACHE/NETWORK/FALLBACK)。 - AppCache 更新:看 manifest 是否变化;新缓存就绪后通常要 刷新 才切换;manifest 自己被缓存会导致“怎么都不更新”。
- Service Worker:
install预缓存,fetch拦截,activate清旧缓存;策略与更新完全可编程,适合 PWA。