浏览器缓存策略
一、缓存概览
1.1 缓存位置 (按优先级)
1. Service Worker Cache
2. Memory Cache (内存缓存)
3. Disk Cache (硬盘缓存)
4. Push Cache (HTTP/2)
5. 网络请求| 缓存类型 | 特点 |
|---|---|
| Memory Cache | 速度快,容量小,关闭 Tab 即失效 |
| Disk Cache | 速度较慢,容量大,可持久化 |
| Service Worker | 可编程控制,离线可用 |
1.2 缓存策略分类
HTTP 缓存
├── 强缓存 (不发请求)
│ ├── Expires
│ └── Cache-Control
└── 协商缓存 (发请求验证)
├── Last-Modified / If-Modified-Since
└── ETag / If-None-Match二、强缓存
2.1 Expires (HTTP/1.0)
http
Expires: Thu, 01 Dec 2024 16:00:00 GMT- 绝对时间,受本地时间影响
- 已被 Cache-Control 取代
2.2 Cache-Control (HTTP/1.1)
http
Cache-Control: max-age=31536000| 指令 | 说明 |
|---|---|
max-age=<seconds> | 缓存有效时长 (秒) |
s-maxage=<seconds> | CDN 缓存时长,优先级高于 max-age |
no-cache | 使用缓存前必须验证 (走协商缓存) |
no-store | 完全不缓存 |
private | 仅浏览器缓存 |
public | 允许代理服务器缓存 |
immutable | 资源不会变化,刷新不会重新验证 |
2.3 常见配置示例
http
# HTML - 不缓存
Cache-Control: no-cache
# JS/CSS - 带 hash,长期缓存
Cache-Control: max-age=31536000, immutable
# API - 不缓存
Cache-Control: no-store
# 图片 - 7 天
Cache-Control: max-age=6048002.4 强缓存命中
请求 → 检查 Cache-Control/Expires
↓
未过期 → 直接返回缓存 (200 from cache)
↓
已过期 → 进入协商缓存三、协商缓存
3.1 Last-Modified / If-Modified-Since
http
# 首次响应
Last-Modified: Wed, 01 Dec 2024 10:00:00 GMT
# 再次请求
If-Modified-Since: Wed, 01 Dec 2024 10:00:00 GMT
# 服务器响应
# 未修改 → 304 Not Modified
# 已修改 → 200 + 新资源缺点:
- 精度只到秒级
- 文件内容没变但修改时间变了,会重新下载
3.2 ETag / If-None-Match
http
# 首次响应
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# 再次请求
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# 服务器响应
# 匹配 → 304 Not Modified
# 不匹配 → 200 + 新资源ETag 类型:
- 强 ETag:
"abc123"- 字节完全相同 - 弱 ETag:
W/"abc123"- 语义相同即可
3.3 优先级
ETag > Last-Modified
Cache-Control > Expires四、缓存流程图
请求资源
↓
有缓存? ─── No ──→ 发送请求 → 获取响应 → 存入缓存 → 返回
│
Yes
↓
强缓存有效? ─── Yes ──→ 返回缓存 (200 from cache)
│
No
↓
协商缓存: 发送请求 (带 If-None-Match / If-Modified-Since)
↓
资源变化? ─── No ──→ 返回 304 → 使用缓存
│
Yes
↓
返回 200 + 新资源 → 更新缓存五、Service Worker 缓存
5.1 基本用法
javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = ['/', '/styles/main.css', '/script/main.js'];
// 安装时缓存
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
// 请求拦截
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => response || fetch(event.request))
);
});5.2 缓存策略
Cache First (缓存优先)
javascript
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
);
});Network First (网络优先)
javascript
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
});Stale While Revalidate (返回缓存,后台更新)
javascript
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cached) => {
const fetchPromise = fetch(event.request).then((network) => {
cache.put(event.request, network.clone());
return network;
});
return cached || fetchPromise;
});
})
);
});六、实践建议
6.1 静态资源策略
# HTML
Cache-Control: no-cache (每次协商)
# 带 hash 的 JS/CSS/图片
Cache-Control: max-age=31536000, immutable
# 不带 hash 的资源
Cache-Control: max-age=86400 (1天)6.2 Nginx 配置示例
nginx
# HTML 不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# 带 hash 的静态资源
location ~* \.(js|css|png|jpg|gif|ico|woff2)$ {
add_header Cache-Control "max-age=31536000, immutable";
}七、高频面试题
Q1: 强缓存和协商缓存的区别?
| 对比项 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否发请求 | 否 | 是 |
| 状态码 | 200 (from cache) | 304 |
| Header | Cache-Control, Expires | ETag, Last-Modified |
Q2: ETag 和 Last-Modified 的区别?
| 对比项 | ETag | Last-Modified |
|---|---|---|
| 精度 | 内容级别 | 秒级 |
| 计算 | 需要计算 hash | 读取文件属性 |
| 优先级 | 高 | 低 |
Q3: no-cache 和 no-store 的区别?
no-cache: 使用缓存前必须验证 (走协商缓存)no-store: 完全不缓存
Q4: 如何实现资源更新?
- 文件名添加 hash:
app.abc123.js - HTML 不缓存或协商缓存
- 静态资源长期强缓存