HTML5 离线存储原理与使用
使用 Service Worker 和 Cache API 实现网页离线访问
问题
HTML5 的离线储存怎么使用,工作原理能不能解释一下?
解答
HTML5 离线存储有两种方案:
- Application Cache(已废弃)- 通过 manifest 文件声明缓存资源
- Service Worker + Cache API(推荐)- 现代离线存储方案
Application Cache(了解即可)
<!-- index.html -->
<!DOCTYPE html>
<html manifest="app.appcache">
<head>
<title>离线应用</title>
</head>
<body>
<h1>Hello Offline</h1>
</body>
</html>
# app.appcache
CACHE MANIFEST
# v1.0.0
# 需要缓存的文件
CACHE:
index.html
styles.css
app.js
# 需要网络访问的文件
NETWORK:
api/
# 离线时的替代页面
FALLBACK:
/ offline.html
⚠️ Application Cache 已被废弃,新项目请使用 Service Worker。
Service Worker + Cache API(推荐)
1. 注册 Service Worker
// main.js - 在页面中注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW 注册成功,作用域:', registration.scope);
})
.catch(error => {
console.log('SW 注册失败:', error);
});
}
2. 编写 Service Worker
// sw.js - Service Worker 文件
const CACHE_NAME = 'my-app-v1';
const CACHE_URLS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/images/logo.png'
];
// 安装阶段:缓存静态资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('缓存资源');
return cache.addAll(CACHE_URLS);
})
.then(() => self.skipWaiting()) // 立即激活
);
});
// 激活阶段:清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
);
}).then(() => self.clients.claim()) // 接管所有页面
);
});
// 拦截请求:优先使用缓存
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中,返回缓存
if (response) {
return response;
}
// 缓存未命中,发起网络请求
return fetch(event.request).then(response => {
// 检查是否是有效响应
if (!response || response.status !== 200) {
return response;
}
// 克隆响应并缓存
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
});
})
.catch(() => {
// 网络失败,返回离线页面
return caches.match('/offline.html');
})
);
});
3. 常用缓存策略
// 策略1:缓存优先(Cache First)
async function cacheFirst(request) {
const cached = await caches.match(request);
return cached || fetch(request);
}
// 策略2:网络优先(Network First)
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
} catch {
return caches.match(request);
}
}
// 策略3:仅缓存(Cache Only)
async function cacheOnly(request) {
return caches.match(request);
}
// 策略4:仅网络(Network Only)
async function networkOnly(request) {
return fetch(request);
}
// 策略5:Stale While Revalidate(返回缓存同时更新)
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
工作原理
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ 浏览器 │ ──► │ Service Worker │ ──► │ 网络 │
│ (页面) │ ◄── │ (代理层) │ ◄── │ (服务器) │
└─────────────┘ └────────┬────────┘ └─────────────┘
│
▼
┌─────────────────┐
│ Cache Storage │
│ (本地缓存) │
└─────────────────┘
- Service Worker 作为浏览器和网络之间的代理
- 拦截所有网络请求(fetch 事件)
- 根据缓存策略决定返回缓存还是发起网络请求
- 离线时直接返回缓存内容
关键点
- Application Cache 已废弃,现代项目使用 Service Worker
- Service Worker 运行在独立线程,不能直接操作 DOM
- 必须在 HTTPS 环境下使用(localhost 除外)
- Service Worker 有生命周期:install → waiting → activate → fetch
- 根据资源类型选择合适的缓存策略:静态资源用缓存优先,API 用网络优先
目录