浏览器缓存:从底层原理到最佳实践
- 缓存全景图:一张图看懂所有缓存关系
- HTTP缓存:强缓存 vs 协商缓存
-
- 强缓存:浏览器自主决策
- 协商缓存:服务端验证
- 强缓存 vs 协商缓存对比:
- 浏览器缓存:Memory Cache vs Disk Cache
-
- Memory Cache:高速内存缓存
-
- 适用场景
- Disk Cache:持久化磁盘缓存
-
- 适用场景
- Service Worker:应用层缓存控制
-
- Service Worker 缓存特点
- Service Worker 缓存策略
- Service Worker 使用场景
- 缓存查找顺序
- 本地存储体系
- Chrome DevTools中的缓存标识
- 现代框架下的缓存最佳实践
-
- Vue 3 + Vite 缓存配置
- React 缓存策略建议
- 常见问题与解决方案
-
- 用户反映看到的是旧版本页面
- 如何优雅地清除缓存?
- 缓存最佳实践总结
- 未来趋势
- 结语
缓存是前端性能优化的核心,但我们真的理解各种缓存之间的关系吗?本篇文章将彻底解析浏览器缓存机制,帮我们建立完整的知识体系。
缓存全景图:一张图看懂所有缓存关系

HTTP缓存:强缓存 vs 协商缓存
HTTP缓存是浏览器缓存体系的核心,分为两大策略:强缓存 和 协商缓存。
强缓存:浏览器自主决策
浏览器检查本地缓存是否在有效期内,若有效则直接使用,不发送任何网络请求。
# 第一次请求:服务器设置缓存策略
GET /static/logo.png
HTTP/1.1 200 OK
Cache-Control: max-age=31536000, public
Expires: Mon, 01 Jan 2024 00:00:00 GMT
# 后续请求:浏览器自主决策(在有效期内)
GET /static/logo.png
# 浏览器检查:max-age=31536000 → 还有效!
# 直接从缓存返回,不发送网络请求
协商缓存:服务端验证
当请求资源时,浏览器携带缓存标识询问服务器资源是否变更,若没有变更,直接从缓存中读取资源。
# 第一次请求:服务器返回资源标识
GET /api/user/profile
HTTP/1.1 200 OK
ETag: "abc123xyz"
Last-Modified: Wed, 21 Oct 2022 07:28:00 GMT
Cache-Control: no-cache
# 第二次请求:带上标识询问服务器
GET /api/user/profile
If-None-Match: "abc123xyz"
If-Modified-Since: Wed, 21 Oct 2022 07:28:00 GMT
# 服务器响应
HTTP/1.1 304 Not Modified
# 资源未变,使用本地缓存
强缓存 vs 协商缓存对比:
| 决策机制 | 浏览器自己决定 | 浏览器和服务器协商 |
| 检查时机 | 优先检查 | 强缓存失效后检查 |
| 网络请求 | 可能完全不发请求 | 一定发送请求(验证) |
| 典型状态码 | 200 (from cache) | 304 (Not Modified) |
| 响应速度 | 极快(直接本地读取) | 较慢(需要网络往返) |
| 适用场景 | 版本固定的静态资源 | 频繁更新的动态资源 |
| 更新时机 | 缓存过期时更新 | 每次请求都验证 |
浏览器缓存:Memory Cache vs Disk Cache
Memory Cache:高速内存缓存
- 位置:浏览器进程内存空间
- 容量:有限,依赖设备内存
- 生命周期:会话级别,标签页关闭则释放
- 特点:读取速度极快(纳秒级)
适用场景
- 当前页面已加载的资源
- 预加载的脚本和样式
- 小体积的Base64图片
Disk Cache:持久化磁盘缓存
- 位置:用户磁盘的缓存目录
- 容量:较大(通常数百MB到数GB)
- 生命周期:长期持久化
- 特点:读取速度较慢(毫秒级),但容量大
适用场景
- 大体积静态资源(图片、字体、视频)
- 跨会话共享的资源
- 低频访问但需要持久化的内容
Service Worker:应用层缓存控制
Service Worker 运行在浏览器后台,作为代理服务器,赋予开发者完全控制缓存的能力。
Service Worker 缓存特点
Service Worker 缓存策略
Service Worker 使用的是典型的缓存优先策略:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 1. 缓存优先
if (cachedResponse) {
// 后台更新缓存
event.waitUntil(
updateCache(event.request)
);
return cachedResponse;
}
// 2. 网络回退
return fetch(event.request)
.then(networkResponse => {
// 缓存新资源
return cacheResponse(event.request, networkResponse);
})
.catch(() => {
// 3. 回退到离线页面
return caches.match('/offline.html');
});
})
);
});
Service Worker 使用场景
| 离线应用 | Cache-First + Network-Fallback | 提供完整的离线体验 |
| 性能优化 | Stale-While-Revalidate | 快速响应+后台更新 |
| 资源预加载 | Precache + Runtime Caching | 减少关键资源加载时间 |
| 不可靠网络 | Network-First + Cache-Fallback | 提升弱网环境体验 |
缓存查找顺序
async function fetchWithCache(request) {
// 1. Service Worker 拦截(最高优先级)
if (navigator.serviceWorker?.controller) {
const swResponse = await checkServiceWorkerCache(request);
if (swResponse) return swResponse;
}
// 2. Memory Cache 检查(最快路径)
const memoryCached = memoryCache.get(request.url);
if (memoryCached && !isExpired(memoryCached)) {
reportCacheHit('memory');
return memoryCached;
}
// 3. Disk Cache 检查(强缓存验证)
const diskCached = await checkDiskCache(request);
if (diskCached) {
if (diskCached.cacheControl === 'immutable' ||
!isExpired(diskCached)) {
// 更新 Memory Cache 提升后续访问速度
memoryCache.set(request.url, diskCached);
reportCacheHit('disk');
return diskCached;
}
}
// 4. 准备网络请求(设置协商缓存头)
const init = prepareRequest(request);
// 5. 发起网络请求
let response = await fetch(request, init);
// 6. 处理响应,决定是否缓存
if (shouldCache(response)) {
await cacheInLayers(request, response.clone());
}
return response;
}
- 强缓存资源:可以同时存在于Memory和Disk Cache,浏览器根据大小和类型智能分配
- 协商缓存资源:主要在Disk Cache,Memory Cache可能不存储或短期存储
- 开发者控制策略:通过HTTP头控制缓存策略,浏览器自动选择存储位置
- 性能考量:小文件强缓存 → Memory Cache极速;大文件强缓存 → Disk Cache持久
本地存储体系
| Cookie | 4KB | 可设置过期时间 | 每次请求自动携带 | 用户认证、服务端状态 |
| LocalStorage | 5-10MB | 永久(除非手动清除) | 同步阻塞 | 用户偏好、应用配置 |
| SessionStorage | 5-10MB | 标签页关闭时清除 | 同步阻塞 | 表单草稿、临时状态 |
| IndexedDB | 数百MB | 永久 | 异步非阻塞 | 大量结构化数据 |
| Cache API | 依赖设备 | Service Worker控制 | 异步 | 网络资源缓存、PWA |
Chrome DevTools中的缓存标识
在Chrome开发者工具中,不同缓存来源有不同的标识:
| (memory cache) | 200 | 内存缓存 | Memory Cache |
| (disk cache) | 200 | 磁盘缓存 | Disk Cache |
| (ServiceWorker) | 200 | Service Worker缓存 | Service Worker Cache |
| 文件大小 | 304 | 协商缓存生效 | 服务器验证通过 |
| 文件大小 | 200 | 新请求 | 网络获取 |
现代框架下的缓存最佳实践
Vue 3 + Vite 缓存配置
vite.config.js 中配置:
export default defineConfig({
build: {
// 生成带hash的文件名,实现长期缓存
rollupOptions: {
output: {
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]'
}
}
},
server: {
headers: {
// 开发环境:禁用缓存
'Cache-Control': 'no-store'
}
}
});
生产环境 Nginx 配置:
server {
# HTML文件:短缓存 + 协商缓存
location = /index.html {
add_header Cache-Control "public, max-age=300, must-revalidate";
}
# 带hash的静态资源:长期缓存 + 不可变
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
# 匹配带hash的文件名
if ($request_uri ~* "\\.([0-9a-f]{8,})\\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$") {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 无hash的资源使用协商缓存
if ($request_uri !~* "\\.([0-9a-f]{8,})\\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$") {
add_header Cache-Control "public, max-age=604800, must-revalidate";
}
}
# API请求:不缓存或短缓存
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
proxy_pass http://api-backend;
}
}
React 缓存策略建议
使用 React Query 进行数据缓存管理:
import { useQuery, QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 10 * 60 * 1000, // 10分钟
retry: 1,
},
},
});
function UserProfile({ userId }) {
const { data, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
// 根据用户交互决定缓存策略
cacheTime: userId ? 15 * 60 * 1000 : 0, // 登录用户缓存15分钟
});
// 预加载相关数据
const prefetchPosts = () => {
queryClient.prefetchQuery({
queryKey: ['posts', userId],
queryFn: () => fetchUserPosts(userId),
});
};
}
常见问题与解决方案
用户反映看到的是旧版本页面
原因:HTML文件被缓存,导致加载的是旧版本的静态资源引用。 解决方案:在nginx中配置合理的缓存策略:
location / {
# HTML文件:短时间缓存 + 协商缓存
add_header Cache-Control "public, max-age=300, must-revalidate";
}
location /assets/ {
# 静态资源:长时间缓存 + 文件hash
add_header Cache-Control "public, max-age=31536000, immutable";
}
如何优雅地清除缓存?
vite/webpack 会自动生成文件hash,以此进行清除。
缓存最佳实践总结
| HTML入口文件 | 协商缓存 | 短时间(5分钟) | 内容hash或版本号 |
| JS/CSS(带hash) | 强缓存 | 长期(1年) | 文件名hash变更 |
| 图片/字体 | 强缓存 | 中期(1个月) | URL变更或版本控制 |
| API数据(列表) | 内存缓存 | 短期(1-5分钟) | 时间过期或主动失效 |
| 用户偏好配置 | LocalStorage | 永久 | 用户操作触发更新 |
| 离线资源 | Service Worker | 长期 | 版本化预缓存 |
未来趋势
- HTTP/3的缓存改进:更智能的缓存协商机制
- AI驱动的缓存策略:根据用户行为预测缓存需求
- 边缘计算缓存:CDN与浏览器缓存的深度融合
- 隐私保护的缓存:沙盒化缓存防止指纹追踪
结语
浏览器缓存是一个多层次的复杂系统,理解各层缓存的工作原理、生命周期和适用场景是进行有效缓存管理的关键,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!
网硕互联帮助中心





评论前必须登录!
注册