Web 缓存策略学习笔记
摘要 :浏览器缓存是前端性能优化的核心手段之一。它通过 HTTP 响应头控制资源是否使用本地缓存,分为强缓存 (直接用,不请求)、协商缓存 (问一下再用)和策略缓存(按场景灵活选择)三大类。理解它们的优先级和触发条件,是掌握 HTTP 缓存的关键。

一、整体流程
浏览器请求一个资源时,缓存判断的完整链路:
sql
发起请求
│
▼
┌─────────────┐ 命中(未过期)
│ 1. 强缓存 │ ──→ 直接使用本地缓存 ✅
│ Expires │ (状态码 200 from cache)
│ Cache-Control│
└──────┬──────┘
未命中 / 已过期
▼
┌─────────────┐ 资源没变
│ 2. 协商缓存 │ ──→ 返回 304,继续用旧缓存 ✅
│ Last-Modified│
│ ETag │
└──────┬──────┘
资源已变
▼
返回新资源 200 + 更新缓存
二、强缓存
核心思想:不向服务器发请求,直接从本地读取。最快,但可能拿到过期资源。
两个关键响应头
| 头部 | 示例 | 说明 |
|---|---|---|
Expires |
Expires: Wed, 21 Oct 2026 07:28:00 GMT |
绝对时间,服务器告诉浏览器"这个时间之前别来问我"。缺点:依赖客户端时间,改了系统时间就失效 |
Cache-Control |
Cache-Control: max-age=3600 |
相对秒数 ,距离现在多少秒内有效。优先级高于 Expires,推荐使用 |
Cache-Control 常用值
http
Cache-Control: max-age=3600 /* 缓存 1 小时 */
Cache-Control: no-cache /* 跳过强缓存,走协商缓存 */
Cache-Control: no-store /* 完全不缓存 */
Cache-control: private /* 只有浏览器可以缓存,代理/CDN 不行 */
Cache-Control: public /* 浏览器和代理/CDN 都可以缓存 */
Cache-Control: immutable /* 资源永不改变(如带 hash 的静态文件)*/
优先级
Cache-Control > Expires
当两者同时存在时,以 Cache-Control 为准。
三、协商缓存
核心思想:强缓存过期后,先问服务器"资源变了没",没变就继续用旧的(返回 304),变了才下载新的。
两对验证字段
| 类型 | 请求头(浏览器问) | 响应头(服务器答) | 原理 |
|---|---|---|---|
| Last-Modified | If-Modified-Since |
Last-Modified |
对比文件的最后修改时间 |
| ETag | If-None-Match |
ETag |
对比文件的唯一标识(哈希值) |
完整交互过程
http
/* 第一次请求(无缓存) */
GET /style.css HTTP/1.1
HTTP/1.1 200 OK
Last-Modified: Mon, 20 Jul 2026 08:00:00 GMT
ETag: "abc123"
Cache-Control: max-age=3600
/* 第二次请求(强缓存已过期,进入协商) */
GET /style.css HTTP/1.1
If-Modified-Since: Mon, 20 Jul 2026 08:00:00 GMT
If-None-Match: "abc123"
/* 服务器判断:资源没变 → 304 */
HTTP/1.1 304 Not Modified
(空 body,节省带宽)
/* 服务器判断:资源变了 → 200 + 新内容 */
HTTP/1.1 200 OK
Last-Modified: Tue, 21 Jul 2026 10:00:00 GMT
ETag: "def456"
Last-Modified vs ETag
| 维度 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级,1 秒内修改检测不到 | 精确到文件内容 hash |
| 局限性 | 文件改了但内容不变也会重新下载;不同服务器时间不一致 | 无此问题 |
| 优先级 | 较低 | 较高(服务器同时返回两者时,优先用 ETag) |
四、策略缓存(Service Worker / Cache API)
核心思想 :前两种缓存都由浏览器自动管理,而策略缓存通过 Service Worker 拦截网络请求,由开发者手动决定每个请求怎么处理------读缓存、走网络、还是两者结合。
Service Worker 工作原理
java
浏览器发起请求
│
▼
Service Worker (拦截)
│
├── 命中自定义缓存 → 直接返回(离线可用)
├── 未命中 → 发起网络请求
│ │
│ ├── 成功 → 返回结果 + 存入缓存(下次可直接用)
│ └── 失败 → 返回缓存的旧版本(降级兜底)
└── 完全不经过 SW → 正常网络请求
典型策略模式
js
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js 中常见的缓存策略
const CACHE_NAME = 'v1';
// 策略 1:Cache First(缓存优先)------ 静态资源用这个
// 先查缓存,有就直接返回,没有才走网络并缓存
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache =>
cache.match(event.request).then(response => {
return response || fetch(event.request).then(netResponse => {
cache.put(event.request, netResponse.clone());
return netResponse;
});
})
)
);
});
// 策略 2:Network First(网络优先)------ API 数据用这个
// 先走网络,失败时回退到缓存(保证数据新鲜度)
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
// 策略 3:Stale While Revalidate(过期但先返回旧版,后台更新)
// 先返回缓存,同时在后台发请求更新缓存
event.respondWith(
caches.open(CACHE_NAME).then(cache =>
cache.match(event.request).then(cached => {
const fetched = fetch(event.request).then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetched;
})
)
);
适用场景
| 场景 | 推荐策略 | 说明 |
|---|---|---|
| HTML 入口文件 | Network First | 保证拿到最新页面 |
| JS/CSS/图片等静态资源 | Cache First 或 Stale While Revalidate | 变更频率低,优先速度 |
| API 接口数据 | Network First | 数据需要实时性 |
| 离线应用 | Cache First + 预缓存 | 断网也能用 |
五、三种缓存对比总结
| 维度 | 强缓存 | 协商缓存 | 策略缓存 (SW) |
|---|---|---|---|
| 是否发请求 | 不发 | 发(带验证头) | 可控制 |
| 状态码 | 200 from cache | 304 Not Modified | 由开发者决定 |
| 控制方 | 浏览器自动 | 浏览器自动 | 开发者手动 |
| 核心字段 | Expires / Cache-Control | Last-Modified / ETag | Cache API |
| 离线支持 | 有(未过期时) | 无 | 完全支持 |
| 灵活性 | 低 | 中 | 最高 |
实际项目中的最佳实践
yaml
HTML 文件:
Cache-Control: no-cache (每次都协商,确保拿最新入口)
JS/CSS/图片(带 hash):
Cache-Control: max-age=31536000, immutable (永久缓存,hash 变了 URL 就变了)
JS/CSS/图片(不带 hash):
Cache-Control: max-age=86400 (缓存 1 天,过期后协商)
API 接口:
Cache-Control: no-cache (不缓存,或用 SW 做 Network First)
六、快速记忆口诀
- 强缓存 :时间没过直接用,
max-age比Expires强 - 协商缓存 :过期了问服务器,
ETag比Last-Modified准 - 策略缓存:Service Worker 拦截请求,想存就存、想取就取