页面加载慢,第一反应是优化代码、压缩资源。但还有一个更容易忽略的点------HTTP缓存。
配置得当的话,二次访问几乎不用发请求,资源直接从本地拿。今天把HTTP缓存的完整机制讲清楚。
强缓存:直接从本地拿,不问服务器
强缓存的意思是:浏览器第一次请求资源后,服务器会告诉浏览器"这个资源你可以缓存多久"。在这个时间内,浏览器再次需要这个资源时,直接从本地读取,不发任何请求。
控制强缓存的两个HTTP头:
| HTTP头 | 出现版本 | 优先级 | 说明 |
|---|---|---|---|
| Cache-Control | HTTP/1.1 | 高 | max-age=3600表示缓存3600秒 |
| Expires | HTTP/1.0 | 低 | 绝对时间,如Wed, 21 Oct 2026 07:28:00 GMT |
两个都存在时,Cache-Control优先。
yaml
HTTP/1.1 200 OK
Cache-Control: max-age=31536000
Expires: Thu, 10 Oct 2027 00:00:00 GMT
Content-Type: application/javascript
// JS文件内容...
常见的Cache-Control指令:
csharp
max-age=3600 # 缓存3600秒
no-cache # 不用强缓存,每次都走协商缓存
no-store # 完全不缓存
public # 中间代理(CDN)也可以缓存
private # 只有浏览器可以缓存,CDN不行
协商缓存:缓存过期了,问一嘴服务器
强缓存过期后,浏览器不会直接重新下载,而是先问服务器:"这个资源有没有更新?"
这就是协商缓存,通过两个请求头实现:
方案一:Last-Modified / If-Modified-Since
yaml
# 第一次请求,服务器返回:
Last-Modified: Wed, 09 Oct 2026 12:00:00 GMT
# 缓存过期后,浏览器带上:
If-Modified-Since: Wed, 09 Oct 2026 12:00:00 GMT
# 服务器比较时间:
# 没改过 → 304 Not Modified(用本地缓存)
# 改过了 → 200 OK + 新内容
方案二:ETag / If-None-Match(推荐)
python
# 第一次请求,服务器返回:
ETag: "abc123def456"
# 缓存过期后,浏览器带上:
If-None-Match: "abc123def456"
# 服务器比较ETag值:
# 一样 → 304 Not Modified
# 不一样 → 200 OK + 新内容 + 新ETag
ETag比Last-Modified更精确------文件内容没变但修改时间变了(比如touch了一下),Last-Modified会误判为"已更新",ETag不会。
完整的缓存决策流程
sql
浏览器请求资源
↓
检查强缓存(Cache-Control/Expires)
↓
未过期 → 直接使用本地缓存(200 from cache)
↓
已过期
↓
发起协商缓存请求(带ETag或Last-Modified)
↓
服务器返回304 → 使用本地缓存
服务器返回200 → 下载新资源
实际项目中的缓存策略
不同类型文件的缓存策略不一样:
| 资源类型 | 缓存策略 | 理由 |
|---|---|---|
| HTML | no-cache |
入口文件,必须每次都验证 |
| JS/CSS(带hash) | max-age=31536000, immutable |
文件名含hash,内容变了hash就变 |
| JS/CSS(不带hash) | no-cache |
需要协商缓存确认版本 |
| 图片/字体 | max-age=86400 |
缓存一天,平衡更新频率和性能 |
| API响应 | no-store |
动态数据,不缓存 |
Webpack打包时给文件名加content hash是最佳实践:
javascript
// webpack.config.js
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js'
}
这样HTML里引用的是app.a1b2c3d4.js,内容变了hash就变,浏览器会请求新文件。旧文件可以永久缓存,不用担心用户拿到旧版本。
开发者工具看缓存
Chrome DevTools的Network面板能看到每个资源的缓存状态:
(disk cache)--- 从磁盘缓存读取(memory cache)--- 从内存缓存读取(from service worker)--- 从SW读取304--- 协商缓存命中,服务器说"没变"
调试时可以勾选"Disable cache"来跳过缓存,方便验证。
HTTP缓存是前端性能优化成本最低、收益最高的手段之一。配好了缓存策略,二次加载时间能砍掉一大半。
建议把自己项目的Network面板打开看看,有多少请求是304,有多少是真正下载的。