浏览器缓存最常见的四组字段是:
Cache-ControlExpiresETag / If-None-MatchLast-Modified / If-Modified-Since
这些字段通常由业务服务、Nginx、CDN、对象存储或静态资源服务器设置,最终通过 HTTP 响应头返回给浏览器。浏览器保存响应后,会在下次请求同一资源时自动应用它们,普通前端代码一般不需要介入。
它们不是四套互相竞争的方案。前两种负责判断缓存能否直接使用,后两种负责在缓存需要验证时判断资源有没有变化。
1. Cache-Control
Cache-Control 是当前最主要的缓存控制字段。例如:
http
Cache-Control: public, max-age=3600
它表示响应可以缓存,并且在一小时内处于新鲜状态。浏览器再次访问时可以直接读取 memory cache 或 disk cache,不需要发起网络请求。
几个容易混淆的指令:
no-cache:可以保存响应,但每次使用前必须向服务器验证。no-store:不应以任何形式保存响应(通常是敏感数据),自然也不能缓存。immutable:有效期内资源不会变化,不需要重新验证。
带内容哈希的 JS、CSS 和图片,常设置较长的 max-age 并配合 immutable。
2. Expires
Expires 通过一个绝对时间表示缓存何时过期:
http
Expires: Mon, 31 Aug 2026 12:00:00 GMT
浏览器发现当前时间早于这个时间,就可以直接使用本地缓存。它依赖客户端时间,而且表达能力不如 Cache-Control,因此主要用于兼容旧的 HTTP 缓存实现。
当 Cache-Control 中已经提供 max-age 或 s-maxage 时,浏览器优先采用 Cache-Control,忽略 Expires 的有效期判断。
3. ETag / If-None-Match
缓存过期,或者响应设置了 no-cache 时,浏览器不能直接使用本地副本。此时如果之前的响应带有:
http
ETag: "v123"
浏览器会自动发起条件请求:
http
If-None-Match: "v123"
服务器或 CDN 判断资源未变化,就返回 304 Not Modified,浏览器继续使用本地响应体;如果资源已经变化,则返回 200 OK 和新内容。
ETag 是资源版本标识,可以由内容 Hash、版本号或其他规则生成,并不保证一定是内容 Hash。304 省去了响应体传输,但仍然有一次网络往返,因此主要节省带宽,不能像本地强缓存一样完全省掉等待时间。
4. Last-Modified / If-Modified-Since
服务端也可以通过最后修改时间提供校验信息:
http
Last-Modified: Sun, 30 Aug 2026 10:00:00 GMT
缓存需要验证时,浏览器自动发送:
http
If-Modified-Since: Sun, 30 Aug 2026 10:00:00 GMT
资源在此后没有修改,服务端返回 304;发生过修改,则返回新的 200 响应。
这种方式实现简单,但修改时间通常只有秒级精度,也无法识别"时间变化但内容没变"等情况,因此准确性一般不如 ETag。
浏览器缓存的先后顺序
一般 Cache-Control 和 Expires 被称作强缓存;ETag 和 Last-Modified 被称作协商缓存。
浏览器拿到一个请求后,大致按照下面的顺序判断:
sql
-- 没有本地缓存 ─→ 请求服务器,获得 200
│
└─ 有本地缓存
│
├─ no-store ─→ 不复用,重新请求
│
├─ 缓存新鲜
│ ├─ Cache-Control: max-age 判断
│ └─ 没有 max-age 时用 Expires
│ └─ 直接使用缓存
│
└─ 已过期或 no-cache
│
├─ 有 ETag ─→ If-None-Match
├─ 否则有 Last-Modified ─→ If-Modified-Since
└─ 两者都没有 ─→ 下载完整响应
如果 ETag 和 Last-Modified 同时存在,浏览器可能同时发送两个条件请求头,但服务器判断时以 If-None-Match 为优先;只有没有 If-None-Match 时,才单独依据 If-Modified-Since。
所以真正需要记住的是两段顺序:
text
判断缓存是否新鲜:Cache-Control > Expires
缓存需要验证:ETag > Last-Modified
最终在 DevTools 中会看到三类结果:缓存仍然新鲜时显示 (memory cache) 或 (disk cache);验证后资源未变化时返回 304;缓存不可用或资源已经变化时返回带完整响应体的 200。
本地缓存命中效果最好,因为没有网络请求;304 次之,它省带宽但保留网络往返;完整 200 的传输成本最高。