7.3 离线应用与Application Cache(已废弃)
7.3 离线应用与Application Cache(已废弃)
重要提示:Application Cache API 已被现代浏览器废弃,本节内容仅作为历史参考和技术考古。实际开发中应使用Service Worker替代。
Application Cache基本原理
1. 缓存机制概述
Application Cache(AppCache)通过清单文件(manifest)定义需要缓存的资源,工作原理如下:
- 浏览器解析清单文件并下载指定资源
- 创建应用缓存副本供离线使用
- 后续访问优先使用缓存资源
- 清单文件变更时触发更新
2. 基本使用方式
HTML启用AppCache:
<!DOCTYPE html>
<html manifest="example.appcache">
<!-- 页面内容 -->
</html>
清单文件示例 (example.appcache):
CACHE MANIFEST
# v1.0.0 - 2023-07-20
# 这是注释,修改注释会触发更新
CACHE:
/css/style.css
/js/app.js
/images/logo.png
/favicon.ico
NETWORK:
/api/
/login
FALLBACK:
/offline.html
Application Cache详细解析
1. 清单文件结构
| 段名 | 作用 |
|---|---|
CACHE MANIFEST |
必须的第一行标识 |
CACHE: |
列举需要缓存的资源(默认段,可省略) |
NETWORK: |
必须在线访问的白名单资源(使用*表示除CACHE外的所有资源都需要网络请求) |
FALLBACK: |
定义离线时的后备页面(格式:在线URL 后备URL) |
2. 缓存更新流程
-
首次访问:
sequenceDiagram 浏览器->>服务器: 请求HTML文档 服务器->>浏览器: 返回HTML+manifest链接 浏览器->>服务器: 请求manifest文件 服务器->>浏览器: 返回manifest内容 浏览器->>服务器: 并行请求所有CACHE资源 服务器->>浏览器: 返回资源内容 浏览器->>本地: 创建应用缓存副本 -
后续更新:
sequenceDiagram 浏览器->>服务器: 检查manifest是否更新 alt 文件已修改 服务器->>浏览器: 返回新manifest 浏览器->>服务器: 重新获取所有资源 浏览器->>本地: 创建新缓存(旧缓存仍可用) 浏览器->>页面: 触发updateready事件 else 文件未修改 服务器->>浏览器: 304 Not Modified 浏览器->>本地: 使用现有缓存 end
完整示例代码
1. 离线应用实现
HTML文件:
<!DOCTYPE html>
<html manifest="clock.appcache">
<head>
<title>离线时钟</title>
<link rel="stylesheet" href="css/clock.css">
</head>
<body>
<h1>离线时钟</h1>
<div id="time"></div>
<div id="status"></div>
<script src="js/clock.js"></script>
<script>
// 缓存状态检测
function handleCacheEvent(e) {
const status = document.getElementById('status');
switch(e.type) {
case 'checking':
status.textContent = '检查更新...';
break;
case 'downloading':
status.textContent = '下载新缓存...';
break;
case 'updateready':
status.textContent = '更新已就绪,请刷新页面';
break;
case 'cached':
status.textContent = '已缓存,可离线使用';
break;
case 'error':
status.textContent = '缓存错误: ' + e.message;
break;
}
}
window.applicationCache.addEventListener('checking', handleCacheEvent);
window.applicationCache.addEventListener('downloading', handleCacheEvent);
window.applicationCache.addEventListener('updateready', handleCacheEvent);
window.applicationCache.addEventListener('cached', handleCacheEvent);
window.applicationCache.addEventListener('error', handleCacheEvent);
</script>
</body>
</html>
clock.appcache:
CACHE MANIFEST
# v1.1.0
CACHE:
css/clock.css
js/clock.js
images/clock-icon.png
NETWORK:
/api/
FALLBACK:
/ /offline.html
2. JavaScript控制接口
// 检查缓存状态
const appCache = window.applicationCache;
// 强制更新
function updateCache() {
appCache.update();
}
// 切换至新缓存
if (appCache.status === appCache.UPDATEREADY) {
appCache.swapCache();
window.location.reload();
}
// 缓存状态常量
console.log('UNCACHED:', appCache.UNCACHED); // 0
console.log('IDLE:', appCache.IDLE); // 1
console.log('CHECKING:', appCache.CHECKING); // 2
console.log('DOWNLOADING:', appCache.DOWNLOADING); // 3
console.log('UPDATEREADY:', appCache.UPDATEREADY); // 4
console.log('OBSOLETE:', appCache.OBSOLETE); // 5
Application Cache的问题与缺陷
1. 主要设计缺陷
-
缓存更新不可靠:
- 清单文件任何变化(包括注释)都会触发全量下载
- 没有部分更新机制
-
缓存污染风险:
- 错误配置可能导致动态内容被缓存
- 难以清理错误缓存
-
白名单机制不灵活:
NETWORK段规则过于简单- 无法实现复杂缓存策略
-
错误处理困难:
- 单个资源加载失败会导致整个缓存失败
- 缺乏细粒度控制
2. 典型问题场景
案例1:缓存动态内容
CACHE MANIFEST
# 错误地将API响应缓存
CACHE:
/api/user
结果:用户永远看到旧的用户数据,即使服务器数据已更新
案例2:忽略必要网络资源
NETWORK:
*
结果:所有未明确缓存的资源都被允许网络访问,可能泄露敏感数据
迁移到Service Worker
1. Service Worker优势对比
| 特性 | Application Cache | Service Worker |
|---|---|---|
| 缓存控制 | 清单文件定义 | 编程式精细控制 |
| 更新机制 | 全量更新 | 增量更新 |
| 请求拦截 | 不支持 | 完全控制网络请求 |
| 调试支持 | 有限 | Chrome DevTools完整支持 |
| 生命周期 | 简单 | 明确的生命周期管理 |
| 兼容性 | 已废弃 | 现代浏览器全面支持 |
2. 迁移示例
原AppCache清单:
CACHE MANIFEST
# v1.0.0
CACHE:
/css/styles.css
/js/app.js
/img/logo.png
NETWORK:
/api/
FALLBACK:
/ /offline.html
等效Service Worker实现 (sw.js):
const CACHE_NAME = 'my-app-v1';
const OFFLINE_URL = '/offline.html';
const PRECACHE_URLS = [
'/',
'/css/styles.css',
'/js/app.js',
'/img/logo.png'
];
// 安装阶段 - 预缓存关键资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting())
);
});
// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
return caches.delete(cache);
}
})
);
}).then(() => self.clients.claim())
);
});
// 请求拦截
self.addEventListener('fetch', event => {
// API请求直接通过网络获取
if (event.request.url.includes('/api/')) {
event.respondWith(fetch(event.request));
return;
}
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
.catch(() => caches.match(OFFLINE_URL))
);
});
历史版本检测与降级
// 检测浏览器支持情况
function checkOfflineSupport() {
if ('serviceWorker' in navigator) {
return 'serviceWorker';
} else if ('applicationCache' in window) {
console.warn('Application Cache is deprecated. Migrate to Service Worker.');
return 'appCache';
} else {
return 'none';
}
}
// 根据支持情况初始化
const support = checkOfflineSupport();
if (support === 'serviceWorker') {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker 注册成功');
});
} else if (support === 'appCache') {
// 仅作演示,实际项目不应使用
window.addEventListener('load', function() {
const appCache = window.applicationCache;
appCache.addEventListener('error', function(e) {
console.error('AppCache 错误:', e.message);
});
});
} else {
console.warn('当前浏览器不支持离线功能');
}
总结:为什么被废弃
- 僵化的缓存策略:无法适应现代Web应用的复杂需求
- 更新机制缺陷:导致不必要的带宽消耗
- 错误处理不足:失败时缺乏恢复手段
- 与现代API冲突:难以与Service Worker等新技术协同工作
- 开发者体验差:调试困难,问题难以排查
迁移建议:
- 现有使用AppCache的项目应尽快迁移到Service Worker
- 新项目不应再使用Application Cache
- 学习现代的Workbox等工具库简化Service Worker开发
虽然Application Cache已经退出历史舞台,但理解其设计思想和缺陷,有助于我们更好地使用现代离线技术构建可靠的Web应用。
#前端开发
分享于 2025-05-20