浏览器缓存机制
浏览器缓存是前端性能优化的重要环节。在请求资源时,浏览器会按照 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 与 Disk Cache
Memory Cache(内存缓存)
Memory Cache 将资源存储在内存中,读取速度极快,但进程关闭即清除(Tab 关闭 = 内存缓存清空)。
特性:
- 速度极快:直接从内存读取,无磁盘 I/O 开销
- 生命周期短:随 Tab 关闭而清除
- 容量有限:内存资源宝贵,存不下大文件
Disk Cache(磁盘缓存)
Disk Cache 将资源存储在磁盘中,读取速度较慢,但容量大、持久化存储。
特性:
- 容量大:可缓存更多资源
- 持久化:Tab 关闭后仍保留
- 速度较慢:有磁盘 I/O 开销
Memory Cache 与 Disk Cache 的选择:浏览器通常会优先命中 Memory Cache(更快),进程关闭后降级到 Disk Cache。开发者可通过 cache-control 的 max-age 等指令间接影响存储位置,但不能强制指定。
Service Worker 缓存
Service Worker 可编程地控制缓存,提供更精细的缓存策略(缓存优先、网络优先等):
- 必须注册后才能工作
生命周期
Service Worker 有严格的生命周期控制,确保站点始终可控:
┌───────────────┐
│ parsed │
└───────┬───────┘
│ Register
▼
┌───────────────┐
│ installing │ ← install 事件触发
└───────┬───────┘
│ install 事件完成
▼
┌───────────────┐
│ installed │ ← waiting 状态,等待旧 SW 释放控制
└───────┬───────┘
│ 旧 SW 不再控制页面
▼
┌───────────────┐
│ activating │ ← activate 事件触发
└───────┬───────┘
│ activate 事件完成
▼
┌───────────────┐
│ activated │ ← 功能完全可用,可拦截请求
└───────┬───────┘
│ 页面关闭后
▼
┌───────────────┐
│ redundant │ ← 已废弃,不再使用
└───────────────┘生命周期事件:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
install | SW 首次安装 | 预缓存核心资源(App Shell) |
activate | SW 激活 | 清理旧缓存、接管控制权 |
fetch | 页面发起网络请求 | 拦截请求、自定义缓存策略 |
message | 接收主线程消息 | 通信、更新缓存 |
push | 接收推送通知 | 离线通知 |
install 事件
// 预缓存核心资源(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);
})
);
});activate 事件
// 清理旧版本缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});fetch 事件
// 拦截请求并返回缓存
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});注册 Service Worker
在主线程中注册 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);
});
});
}注意事项:
- SW 的作用域默认为其所在目录及其子目录,可通过
scope参数调整 - SW 脚本文件应放在站点根目录以获得最大作用域
- 页面关闭后 SW 不会立即销毁,可继续运行处理 push、sync 等事件
- SW 更新时会进入 waiting 状态,直到所有使用旧 SW 的页面关闭
缓存策略
Service Worker 的缓存策略决定了从缓存还是网络获取资源,常用策略如下:
Cache First(缓存优先)
优先从缓存读取,缓存未命中时回退到网络。适合不常变化的资源。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
});Network First(网络优先)
优先从网络获取,网络失败时回退到缓存。适合经常更新的资源。
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);
})
);
});Stale While Revalidate(后台更新)
先返回缓存内容(即使过期),同时在后台发起网络请求更新缓存。下次访问时使用更新后的内容。适合对实时性要求不高但希望尽快展示的资源。
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;
})
);
});Cache Only(仅缓存)
只从缓存获取,不发起网络请求。适合App Shell 等静态资源。
Network Only(仅网络)
只从网络获取,不使用缓存。适合非 GET 请求或实时性要求极高的场景。
策略选型指南
| 策略 | 适用资源类型 | 特点 |
|---|---|---|
| Cache First | 字体、图片、静态资源 | 速度快,离线可用 |
| Network First | HTML、API 请求 | 内容最新,离线兜底 |
| Stale While Revalidate | CSS、JS、非核心 API | 速度快且内容逐步更新 |
| Cache Only | 预缓存的 App Shell | 离线可用 |
| Network Only | 非 GET 请求 | 数据实时 |
更新机制
Service Worker 文件的更新检测由浏览器自动完成:
- 字节级对比:浏览器访问 SW 文件时进行字节对比,任何变化都触发更新
- 安装新 SW:新 SW 进入 installing 状态
- 等待接管:旧 SW 控制的页面全部关闭后,新 SW 才能激活
- 强制更新:调用
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 是 HTTP/2 中新增的缓存阶段,当服务器主动推送资源时,浏览器将其暂存于 Push Cache。
特性:
- 生命周期极短:会话级别,关闭会话即释放
- 可被覆盖:同一资源多次推送,以最后一次为准
- 请求匹配:仅匹配完全相同的请求(URL + 请求头)
- 优先级最低:位于缓存查找链的最末端
- 受 HTTP 缓存首部影响:Cache-Control、max-age 等字段对推送资源同样生效
Push Cache 在实际应用中使用较少,了解其存在即可。
浏览器缓存完整流程
综合 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. 发起网络请求