前言
HTTP 缓存里最重要的两个概念是:
1 | 强缓存:不问服务器,直接用本地缓存。 |
很多人会问:
浏览器怎么知道这次请求要不要走强缓存?
答案是:
浏览器第一次拿到响应时,会把响应体和响应头一起存下来;下次请求同一个资源前,根据之前保存的缓存规则判断缓存是否仍然新鲜。
1. 第一次请求时,浏览器存了什么
第一次请求:
1 | GET /app.js |
服务端返回:
1 | 200 OK |
浏览器会缓存两类东西:
1 | 响应体:console.log("hello") |
同时浏览器会记录:
1 | 这个资源什么时候缓存的 |
所以强缓存判断来自“上一次响应”,不是这次请求凭空决定的。
2. 第二次请求前,浏览器先查缓存
当页面再次需要:
1 | /app.js |
浏览器会先用 URL 等信息去查本地缓存。
如果找到缓存记录,它会看之前存下来的响应头:
1 | Cache-Control: max-age=3600 |
然后判断:
1 | 现在距离上次响应时间有没有超过 3600 秒? |
如果没有超过:
1 | 缓存仍然新鲜 |
浏览器就直接使用本地缓存,不发请求。
这就是强缓存。
3. 强缓存是什么
强缓存命中时,浏览器不会发请求到服务器。
它直接从本地缓存拿资源。
Network 面板里可能看到:
1 | (from memory cache) |
强缓存主要由这些响应头控制:
1 | Cache-Control: max-age=31536000 |
其中 Cache-Control 是 HTTP/1.1 更常用的缓存控制方式。
4. Cache-Control 怎么控制强缓存
最典型的是:
1 | Cache-Control: max-age=3600 |
意思是:
1 | 从响应生成开始,3600 秒内缓存是新鲜的。 |
在这 3600 秒内,浏览器再次请求这个资源:
1 | 直接使用本地缓存 |
这就是强缓存。
常见指令:
1 | Cache-Control: max-age=3600 |
含义是:
1 | max-age:缓存有效时间。 |
注意:
1 | no-cache 不是“不缓存” |
它是:
1 | 可以存,但每次使用前都要协商确认。 |
真正不缓存是:
1 | Cache-Control: no-store |
5. Expires 是什么
Expires 是 HTTP/1.0 的缓存头。
比如:
1 | Expires: Wed, 22 Jun 2027 12:00:00 GMT |
意思是:
1 | 在这个时间之前,缓存有效。 |
但它依赖客户端本地时间。
如果用户电脑时间不准,判断可能出错。
所以 HTTP/1.1 更推荐:
1 | Cache-Control: max-age=3600 |
如果 Cache-Control 和 Expires 同时存在,通常 Cache-Control 优先。
6. 强缓存过期后怎么办
如果超过了 max-age:
1 | 缓存不新鲜了。 |
浏览器不能直接用强缓存。
它会进入协商缓存。
如果之前缓存里有:
1 | ETag: "abc123" |
浏览器会带上:
1 | If-None-Match: "abc123" |
如果之前缓存里有:
1 | Last-Modified: Wed, 22 Jun 2026 10:00:00 GMT |
浏览器会带上:
1 | If-Modified-Since: Wed, 22 Jun 2026 10:00:00 GMT |
服务端判断资源没变,返回:
1 | 304 Not Modified |
浏览器继续使用本地缓存。
服务端判断资源变了,返回:
1 | 200 OK |
并带上新的响应体,浏览器更新缓存。
7. 协商缓存是什么
协商缓存命中时,浏览器会发请求到服务器,但服务端可以不返回完整资源。
流程是:
1 | 浏览器:我本地有一份资源,它还是新的吗? |
服务器返回:
1 | 304 Not Modified |
没有响应体,浏览器继续用本地缓存。
协商缓存主要有两组头:
1 | Last-Modified / If-Modified-Since |
8. Last-Modified / If-Modified-Since
第一次请求,服务端返回:
1 | Last-Modified: Wed, 22 Jun 2026 10:00:00 GMT |
意思是:
1 | 这个资源最后修改时间是这个。 |
下次请求,浏览器带上:
1 | If-Modified-Since: Wed, 22 Jun 2026 10:00:00 GMT |
服务端判断:
1 | 资源从这个时间后没变 -> 304 |
缺点是:
1 | 时间精度有限。 |
9. ETag / If-None-Match
第一次请求,服务端返回:
1 | ETag: "abc123" |
意思是:
1 | 这是资源当前版本标识。 |
下次请求,浏览器带上:
1 | If-None-Match: "abc123" |
服务端判断:
1 | ETag 一样 -> 304 |
ETag 可以基于:
1 | 文件内容 hash |
所以它通常比 Last-Modified 更精确。
如果两者同时存在,通常:
1 | ETag / If-None-Match 优先级更高 |
10. 强缓存和协商缓存的完整流程
浏览器请求资源时,大致流程是:
1 | 1. 先看本地有没有缓存记录 |
顺序是:
1 | 强缓存优先 |
11. 哪些信息会参与缓存匹配
浏览器一般会用资源 URL 作为缓存 key 的核心。
比如:
1 | https://cdn.example.com/app.js |
如果 URL 变了:
1 | /app.js?v=1 |
对浏览器来说通常就是两个不同资源。
所以前端静态资源常用 hash 文件名:
1 | app.a1b2c3.js |
内容变了,URL 变了,浏览器自然请求新资源。
内容没变,URL 不变,继续走缓存。
12. 常见缓存策略
带 hash 的静态资源
比如:
1 | app.8f3a9c.js |
文件名里有内容 hash,内容变了文件名也变。
可以设置很长强缓存:
1 | Cache-Control: public, max-age=31536000, immutable |
HTML 文件
HTML 通常不能强缓存太久,因为它要引用最新的 JS/CSS。
常见策略是:
1 | Cache-Control: no-cache |
意思是:
1 | 可以缓存,但每次使用前要和服务器确认。 |
接口数据
要看业务。
用户信息、订单状态这类可能用:
1 | Cache-Control: no-store |
公共、变化不频繁的数据可以适当缓存。
13. 用户刷新会不会影响强缓存
会。
不同刷新方式可能不一样:
1 | 普通访问 / 页面跳转: |
如果 DevTools 勾选了:
1 | Disable cache |
缓存行为也会被禁用。
14. 常见误区
no-cache 是不缓存
不是。
1 | Cache-Control: no-cache |
表示:
1 | 可以缓存,但用之前必须协商。 |
真正不缓存是:
1 | Cache-Control: no-store |
304 是没有走网络
不是。
304 是协商缓存,已经发了请求,只是服务端没有返回完整响应体。
缓存只发生在浏览器
不一定。
缓存可能发生在:
1 | 浏览器 |
排查缓存问题时要看完整链路。
15. 一句话总结
可以这样记:
1 | 强缓存:缓存没过期,不发请求,直接用本地缓存。 |
浏览器怎么知道要不要走强缓存?
1 | 靠上一次响应里保存下来的 Cache-Control / Expires 等缓存规则。 |
最短记法:
强缓存看新鲜度,协商缓存问版本号。