「为什么改了 CSS 用户却看到旧样式?」「为什么有的请求返回 304?」这些都绕不开 HTTP 缓存。它分两层:强缓存和协商缓存。搞清楚这两层,就能精准控制资源的更新与复用。
强缓存:连请求都不发
强缓存命中时,浏览器直接用本地副本,根本不和服务器通信。控制它的是两个响应头:
Cache-Control(HTTP/1.1,优先级高):max-age=31536000:缓存有效期(秒)。no-cache:可以缓存,但每次用前必须走协商缓存验证。no-store:完全不缓存。immutable:在有效期内永不重新验证(即使用户刷新)。
Expires(HTTP/1.0,绝对时间,已基本被Cache-Control取代)。
命中强缓存时,DevTools 的 Network 里 Size 一列会显示 disk cache 或 memory cache,状态码仍是 200(带灰色标注)。
协商缓存:问一句「变了没」
当强缓存过期(或设了 no-cache),浏览器会带上「凭证」去问服务器:我手里这份还能用吗?这就是协商缓存。一对头:
基于内容指纹(ETag)
1 | 响应:ETag: "a1b2c3" |
服务器比对 ETag,没变就返回 304 Not Modified(不带 body),浏览器继续用本地副本;变了就返回 200 + 新内容。
基于修改时间(Last-Modified)
1 | 响应:Last-Modified: Wed, 16 Apr 2026 08:00:00 GMT |
ETag 比 Last-Modified 更精确(后者只能精确到秒,且文件内容没变只是 mtime 变了也会误判),两者同时存在时 ETag 优先。
一张决策图
graph TD A[请求资源] --> B{强缓存有效?} B -->|是| C[用本地副本 200 from cache] B -->|否| D[带 ETag/Last-Modified 询问] D --> E{服务器: 变了吗} E -->|没变| F[304 用本地副本] E -->|变了| G[200 返回新内容]
实战策略
现代前端的标准做法是「文件名带 hash + 长缓存」:
- 带指纹的静态资源(
app.3f9a.js):Cache-Control: max-age=31536000, immutable。内容一变文件名就变,URL 变了自然重新下载,所以可以放心长期强缓存。 - HTML 入口文件:
Cache-Control: no-cache。让浏览器每次都验证,这样一旦发布新版,HTML 里引用的新 hash 文件名就能立刻生效。
这套组合拳既最大化复用、又保证更新及时。开头那个「改了 CSS 看到旧样式」的问题,根因往往就是 CSS 用了固定文件名又设了长 max-age——改成 hash 文件名即可根治。
小结
强缓存决定「要不要发请求」,协商缓存决定「发了之后要不要传 body」。静态资源用 hash 文件名配长强缓存,HTML 用 no-cache,是兼顾性能与更新及时性的经典方案。