# 浏览器 HTTP 缓存:强缓存、协商缓存与启发式缓存

4 阅读5分钟

一、强缓存(强制缓存)

1.1 定义与现象

当响应在浏览器侧仍被视为有效时,浏览器可以不发起网络请求(或仅做非常有限的本地判断),直接使用本地副本,开发者工具里常见:

  • 200 (from memory cache):从内存读取,多见于脚本、图片、字体等;关闭标签页或浏览器后通常释放
  • 200 (from disk cache):从磁盘读取,多见于 CSS 等;关闭浏览器后仍可能保留(与实现与策略有关)。

1.2 控制强制缓存的响应头

强缓存主要由响应头中的过期信息控制,常见三类:

响应头协议/背景作用简述
ExpiresHTTP/1.0绝对过期时间(GMT)。依赖客户端与服务器时钟一致,时钟偏差会导致提前/延后过期。
Cache-ControlHTTP/1.1现代首选,指令丰富,可表达 max-ageno-storeno-cachepublic/private 等。
PragmaHTTP/1.0 遗留常见为 Pragma: no-cache,多用于兼容;在响应中的语义与「禁用缓存/需验证」类需求相关(见下文误区)。

Expires:

值为服务器返回的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。(HTTP1.0的属性,缺点是客户端和服务器时间不一致会导致命中误差)

Cache-Control 常用指令(节选)

  • no-store不存储响应(或等价于禁止进入缓存),最「干净」的禁用方式之一。
  • no-cache可以存,但使用缓存前必须先向源服务器做验证(走协商缓存/重新验证),不是「完全不缓存」。
  • private / publicprivate 表示响应仅供单个用户的私有缓存使用;public 允许共享缓存(如 CDN)也缓存该响应。
  • max-age=<秒>:从收到响应的时刻起算,在这么多秒内视为可用(与 Expires 相比不依赖绝对时钟)。
  • must-revalidate:一旦超过缓存寿命,不得在未验证的情况下使用过期副本(与 proxy-revalidate 针对共享缓存不同)。

扩展指令(补充)

  • immutable:在 max-age 有效期内资源不会改变;配合带哈希的文件名(如 app.abc123.js)可减少无意义的条件请求(浏览器支持度需结合实际版本验证)。
  • stale-while-revalidate=<秒>:允许在过期后的宽限期内先返回旧内容,同时后台再验证更新(改善体感延迟)。
  • stale-if-error=<秒>:源站错误时,在限定时间内仍可使用旧缓存(多见于 CDN/代理场景的配置思路)。

Pragma:

no-cache:效果和cache-control等no-cache一致。

1.3 ExpiresCache-ControlPragma 的优先级

Pragma > Cache-Control > Expires

  • 若存在 Cache-Control 且包含明确的新鲜度信息(如 max-age),通常 max-age 会优先生效,并覆盖同响应中的 Expires(二者冲突时以 Cache-Control 为准)。
  • Pragma 主要用于 HTTP/1.0 向后兼容;no-cache作为兜底方案

二、协商缓存(对比缓存 / 重新验证)

2.1 定义与现象

当本地副本过期或响应声明 no-cache(必须先验证) 时,浏览器仍会发起请求,但在请求头中携带缓存验证字段。若服务器判断资源未变,则返回 304 Not Modified响应体通常为空,浏览器用本地缓存拼出最终资源。

2.2 两组验证头

(1)Last-Modified / If-Modified-Since

  • 首次响应:Last-Modified 表示资源最后修改时间(精度一般为)。
  • 再次请求:浏览器带上 If-Modified-Since(一般为上次的 Last-Modified)。
  • 服务器:若自该时间以来未修改 → 304;否则 200 并返回新内容与新的 Last-Modified

局限:秒级精度、某些场景下「改了文件但时间戳未变」或批量更新会导致误判风险;对动态资源也不够稳健。

(2)ETag / If-None-Match

  • 首次响应:ETag 为资源的实体标签(常为哈希或版本标记,由服务器生成)。
  • 再次请求:浏览器带上 If-None-Match(上次的 ETag)。
  • 服务器:若 ETag 仍匹配 → 304;否则 200 与新 ETag

优先级(常见实现)ETag 验证通常优先于 Last-Modified(二者同时存在时,服务端多以 If-None-Match 为准)。仍建议以具体服务端(Nginx/OSS/框架)行为为准。

小提示:ETag 在集群或多机部署时要注意生成算法一致,否则容易出现「同内容不同 ETag」导致缓存命中率下降。


三、刷新方式对缓存的影响

下列行为对「强缓存 / 协商缓存是否仍可用」的典型归纳(以 Chromium 系与常见教材结论为主,以 DevTools Network 面板为准):

操作强缓存协商缓存
地址栏回车、<a> 跳转、新开窗口、前进/后退通常有效通常有效
F5(普通刷新)通常绕过/失效通常仍可通过验证命中 304
Ctrl + F5(硬刷新,Mac 上多为 Cmd+Shift+R)通常失效通常失效(可能带 Cache-Control: no-cache 等禁用语义)

四、如何「尽量不走缓存」或强制重新验证

4.1 服务端(推荐)

  • 使用 Cache-Control: no-store:不存或等价禁止缓存(最彻底之一)。
  • 使用 Cache-Control: no-cache:可存,但每次使用前要验证
  • 组合示例(常见表述):Cache-Control: no-cache, no-store, must-revalidate(按需求裁剪,避免无意义堆砌)。

4.2 客户端/调试手段(了解即可)

  • 修改验证头(如 If-Modified-Since: 0、篡改 If-None-Match)可能迫使服务端走不同分支——这属于调试或特殊客户端行为,生产页面不应依赖。
  • 开发时更推荐:DevTools Disable cache版本化文件名query 版本号(注意 CDN 缓存键)、正确的响应头策略

五、启发式缓存(Heuristic Caching)

当响应没有通过 Cache-Control: max-ageExpires 给出明确寿命时,浏览器可能根据规范允许的规则自行估算还能缓存多久。

5.1 与材料中公式的关系

材料中描述:在没有明确强缓存头时,可能参考 Last-Modified,用「当前时间与 Last-Modified 的间隔」乘以约 0.1 作为缓存时间。这与 RFC 7234 中的思路一致:当缺少显式过期时,缓存可以使用启发式新鲜度;若存在 Last-Modified,常用做法是取资源已存在时间的一定比例作为新鲜度寿命的估计——教材与文章里常概括为 10% 规则。