浏览器缓存是前端性能优化的重要环节。在请求资源时,浏览器会按照 Service Worker → Memory Cache → Disk Cache → Push Cache 的顺序查找缓存。本章聚焦浏览器层面的缓存机制,包括 Memory/Disk Cache 的存储规则、Service Worker 缓存策略,以及 HTTP/2 Push Cache。
从浏览器发起请求到最终展示,缓存查找遵循以下顺序:
浏览器请求资源
│
▼
┌─────────────────┐
│ Service Worker │── 命中 → 从 SW Cache 返回
│ Cache │── 未命中 ↓
└─────────────────┘
│
▼
┌─────────────────┐
│ Memory Cache │── 命中 → 从内存读取(极快)
│(内存缓存) │── 未命中 ↓
└─────────────────┘
│
▼
┌─────────────────┐
│ Disk Cache │── 命中 → 从磁盘读取
│(磁盘缓存) │── 未命中 ↓
└─────────────────┘
│
▼
┌─────────────────┐
│ Push Cache │── 命中 → 使用推送缓存
│(HTTP/2 推送) │── 未命中 ↓
└─────────────────┘
│
▼
发起网络请求Memory Cache 和 Disk Cache 本质上属于 HTTP 缓存(受 Cache-Control 等首部字段控制),只是存储位置不同。
Memory Cache 将资源存储在内存中,读取速度极快,但进程关闭即清除(Tab 关闭 = 内存缓存清空)。
特性:
Disk Cache 将资源存储在磁盘中,读取速度较慢,但容量大、持久化存储。
特性:
Memory Cache 与 Disk Cache 的选择:浏览器通常会优先命中 Memory Cache(更快),进程关闭后降级到 Disk Cache。开发者可通过 cache-control 的 max-age 等指令间接影响存储位置,但不能强制指定。
Service Worker 可编程地控制缓存,提供比 HTTP 缓存更精细的缓存策略(缓存优先、网络优先等),且独立于页面运行——使用前必须先注册。
Service Worker 有严格的生命周期控制,确保站点始终可控:
┌───────────────┐
│ parsed │
└───────┬───────┘
│ Register
▼
┌───────────────┐
│ installing │ ← install 事件触发
└───────┬───────┘
│ install 事件完成
▼
┌───────────────┐
│ installed │ ← waiting 状态,等待旧 SW 释放控制
└───────┬───────┘
│ 旧 SW 不再控制页面
▼
┌───────────────┐
│ activating │ ← activate 事件触发
└───────┬───────┘
│ activate 事件完成
▼
┌───────────────┐
│ activated │ ← 功能完全可用,可拦截请求
└───────┬───────┘
│ 被新 SW 替换或 unregister()
▼
┌───────────────┐
│ redundant │ ← 已废弃,不再控制页面
└───────────────┘生命周期事件:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
install | SW 首次安装 | 预缓存核心资源(App Shell) |
activate | SW 激活 | 清理旧缓存、接管控制权 |
fetch | 页面发起网络请求 | 拦截请求、自定义缓存策略 |
message | 接收主线程消息 | 通信、更新缓存 |
push | 接收推送通知 | 离线通知 |
// 预缓存核心资源(App Shell)
const CACHE_NAME = 'v1';
const CACHE_URLS = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(CACHE_URLS);
})
);
});// 清理旧版本缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});// 拦截请求并返回缓存
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});在主线程中注册 SW,需注意几个关键点:
// 检查浏览器支持
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.then((registration) => {
console.log('SW 注册成功:', registration.scope);
})
.catch((error) => {
console.log('SW 注册失败:', error);
});
});
}注意事项:
scope 参数调整Service Worker 的缓存策略决定了从缓存还是网络获取资源,常用策略如下:
优先从缓存读取,缓存未命中时回退到网络。适合不常变化的资源。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
});优先从网络获取,网络失败时回退到缓存。适合经常更新的资源。
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
// 网络成功,更新缓存
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clone);
});
return response;
})
.catch(() => {
// 网络失败,回退缓存
return caches.match(event.request);
})
);
});先返回缓存内容(即使过期),同时在后台发起网络请求更新缓存。下次访问时使用更新后的内容。适合对实时性要求不高但希望尽快展示的资源。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
return cachedResponse || fetchPromise;
})
);
});只从缓存获取,不发起网络请求。适合App Shell 等静态资源。
只从网络获取,不使用缓存。适合非 GET 请求或实时性要求极高的场景。
| 策略 | 适用资源类型 | 特点 |
|---|---|---|
| Cache First | 字体、图片、静态资源 | 速度快,离线可用 |
| Network First | HTML、API 请求 | 内容最新,离线兜底 |
| Stale While Revalidate | CSS、JS、非核心 API | 速度快且内容逐步更新 |
| Cache Only | 预缓存的 App Shell | 离线可用 |
| Network Only | 非 GET 请求 | 数据实时 |
Service Worker 文件的更新检测由浏览器自动完成:
registration.update() 或 self.skipWaiting() + clients.claim()// sw.js 中跳过等待
self.addEventListener('install', (event) => {
self.skipWaiting(); // 跳过 waiting 状态,直接激活
});
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim()); // 立即接管所有页面
});注意:
skipWaiting()+clients.claim()组合可能导致资源不一致(新 SW 的缓存策略与旧页面加载的资源不匹配),生产环境慎用。
Push Cache 是 HTTP/2 中新增的缓存阶段,当服务器主动推送资源时,浏览器将其暂存于 Push Cache。
特性:
Push Cache 在实际应用中使用较少(Chrome 106 起已移除对 HTTP/2 Server Push 的支持),了解其存在即可。
综合 HTTP 缓存与浏览器四层缓存,一次完整的资源请求流程如下:
1. 浏览器发起请求
2. 检查 Service Worker 缓存
├── 命中 → 返回缓存响应
└── 未命中 ↓
3. 检查 Memory Cache
├── 命中 → 从内存返回
└── 未命中 ↓
4. 检查 Disk Cache
├── 命中 → 检查强缓存是否过期
│ ├── 未过期 → 200 (from disk cache) ✓
│ └── 已过期 → 携带 ETag/Last-Modified 协商缓存
│ ├── 304 → 从缓存读取 ✓
│ └── 200 → 返回新资源并更新缓存
└── 未命中 ↓
5. 检查 Push Cache(HTTP/2)
├── 命中 → 返回推送缓存
└── 未命中 ↓
6. 发起网络请求