跳到主要内容

HTTP 缓存:强缓存、协商缓存、缓存控制头怎么串起来讲?

面试速答(30 秒版 TL;DR)

  • HTTP 缓存核心分两层:强缓存协商缓存
  • 强缓存命中时,浏览器连请求都不发,直接用本地副本;常见头是 Cache-Control 和历史上的 Expires
  • 强缓存没命中后,会进入协商缓存;浏览器带上条件请求头去问服务端资源是否变化,常见组合是 ETag / If-None-MatchLast-Modified / If-Modified-Since
  • 现代工程里优先级最高的是 Cache-Control,而协商缓存里更稳的是 ETag,因为它比时间戳更精细。
  • 面试最重要的是讲出一个完整链路:先看强缓存,再走协商缓存,最终决定是 200 还是 304

先建立完整链路

这个流程图几乎就是面试标准答案。


一、强缓存(Strong Cache)

定义

浏览器本地认为缓存仍然有效,直接使用缓存副本,不与服务器通信

常见响应头

Cache-Control

现代最常用,也是优先级最高的缓存控制头。

常见指令:

  • max-age=3600:资源在 3600 秒内可直接使用
  • public:共享缓存也可缓存
  • private:只允许浏览器私有缓存
  • no-cache:不是“不缓存”,而是使用前必须向服务器再验证
  • no-store:不缓存任何内容
  • immutable:在有效期内可认为内容不会变

Expires

HTTP/1.0 时代的绝对时间写法,例如:

Expires: Wed, 26 Mar 2026 10:00:00 GMT

问题是它依赖客户端和服务端时间同步,所以现代通常用 Cache-Control 为主。


二、协商缓存(Conditional Cache)

定义

本地缓存过期后,浏览器不会立刻放弃缓存,而是带着校验条件问服务器:

  • 如果资源没变,返回 304
  • 如果资源变了,返回 200 和新资源

两组常见机制

1. Last-Modified / If-Modified-Since

服务端告诉客户端:这个资源上次修改时间是什么。
下次请求时客户端带上这个时间,服务端判断是否更新过。

优点:

  • 实现简单

缺点:

  • 时间粒度通常只到秒,可能不够精细
  • 文件内容没变但元数据时间变了,也可能误判

2. ETag / If-None-Match

服务端给资源分配一个版本标识(ETag),可以基于内容摘要、版本号或文件指纹生成。
下次请求时客户端带上这个 ETag,让服务端判断是否一致。

优点:

  • 更精确

缺点:

  • 服务端要生成和比较标识,有额外成本

面试建议:

  • 说“现代工程通常 Cache-Control + ETag 组合更常见”会更稳。

三、常见头字段怎么组合

1. 静态资源:哈希文件名 + 长缓存

例如构建产物:

  • app.a1b2c3.js
  • vendor.9f8e7d.css

这类资源通常内容一变,文件名就变,所以可以放心给长缓存:

Cache-Control: public, max-age=31536000, immutable

2. HTML 文档:短缓存或不强缓存

因为 HTML 通常引用最新资源索引,不能长时间被旧副本卡住。常见策略:

Cache-Control: no-cache
ETag: "html-v123"

这样浏览器每次会先协商,但如果内容没变,仍可返回 304

3. 接口响应:按业务谨慎缓存

接口缓存要看数据新鲜度和用户隔离性:

  • 用户私有数据通常用 private
  • 金融/敏感接口常用 no-store
  • 可复用配置项、字典数据可以设置短缓存

四、浏览器为什么要分强缓存和协商缓存

因为目标有两个:

  1. 尽量少发请求,节省 RTT 和带宽
  2. 又不能长期拿到过期数据

强缓存偏向“省请求”,协商缓存偏向“在省流量的同时确保内容没变”。


五、前端常见实战场景

1. 为什么打包工具喜欢给文件名加 hash

因为这样能把“是否变化”的判断交给 URL 本身:

  • URL 不变 -> 可以放心长缓存
  • URL 变化 -> 浏览器必然拉新资源

这比频繁协商更高效。

2. 为什么 HTML 一般不做一年强缓存

因为 HTML 是资源入口。
如果入口页自己被强缓存太久,可能导致用户一直拿不到新版本资源引用。

3. 为什么 DevTools 里有时看到 from memory cachefrom disk cache304

它们不是一回事:

  • from memory cache:直接命中内存缓存
  • from disk cache:直接命中磁盘缓存
  • 304:请求已经发出,服务端确认资源未改

高频题标准答法

1. no-cacheno-store 有什么区别?

  • no-cache:可以缓存,但使用前必须向服务器验证。
  • no-store:不允许缓存,连本地副本都不该保留。

2. ETag 一定比 Last-Modified 好吗?

通常更精确,但不是绝对更优。
如果资源量极大、ETag 生成成本高,工程上也会权衡;很多场景会两者并存。

3. 为什么协商缓存返回 304 也算一次请求?

因为浏览器确实发出了请求,只是服务端告诉它资源没变,不需要再传响应体。

4. 强缓存命中后状态码是多少?

前端在 DevTools 里常看到 (memory cache)(disk cache),严格来说这时没有完整网络请求往返,不是标准意义上的服务端状态码响应。


易错点 / 坑

  • no-cache 等于“不缓存”。
  • 304 当成失败。
  • 只会背缓存头,不会结合“静态资源 vs HTML vs 接口”的实际策略。
  • 忽略共享缓存和私有缓存的差异。

速记要点(可背诵)

  • 缓存链路:先强缓存,后协商缓存
  • 强缓存看 Cache-Control / Expires
  • 协商缓存看 ETag / If-None-MatchLast-Modified / If-Modified-Since
  • 静态资源通常:hash 文件名 + 长缓存;HTML 通常:短缓存或协商缓存