HTTP 缓存:强缓存、协商缓存、缓存控制头怎么串起来讲?
面试速答(30 秒版 TL;DR)
- HTTP 缓存核心分两层:强缓存 和 协商缓存。
- 强缓存命中时,浏览器连请求都不发,直接用本地副本;常见头是
Cache-Control和历史上的Expires。 - 强缓存没命中后,会进入协商缓存;浏览器带上条件请求头去问服务端资源是否变化,常见组合是
ETag / If-None-Match和Last-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.jsvendor.9f8e7d.css
这类资源通常内容一变,文件名就变,所以可以放心给长缓存:
Cache-Control: public, max-age=31536000, immutable
2. HTML 文档:短缓存或不强缓存
因为 HTML 通常引用最新资源索引,不能长时间被旧副本卡住。常见策略:
Cache-Control: no-cache
ETag: "html-v123"
这样浏览器每次会先协商,但如果内容没变,仍可返回 304。
3. 接口响应:按业务谨慎缓存
接口缓存要看数据新鲜度和用户隔离性:
- 用户私有数据通常用
private - 金融/敏感接口常用
no-store - 可复用配置项、字典数据可以设置短缓存
四、浏览器为什么要分强缓存和协商缓存
因为目标有两个:
- 尽量少发请求,节省 RTT 和带宽
- 又不能长期拿到过期数据
强缓存偏向“省请求”,协商缓存偏向“在省流量的同时确保内容没变”。
五、前端常见实战场景
1. 为什么打包工具喜欢给文件名加 hash
因为这样能把“是否变化”的判断交给 URL 本身:
- URL 不变 -> 可以放心长缓存
- URL 变化 -> 浏览器必然拉新资源
这比频繁协商更高效。
2. 为什么 HTML 一般不做一年强缓存
因为 HTML 是资源入口。
如果入口页自己被强缓存太久,可能导致用户一直拿不到新版本资源引用。
3. 为什么 DevTools 里有时看到 from memory cache、from disk cache、304
它们不是一回事:
from memory cache:直接命中内存缓存from disk cache:直接命中磁盘缓存304:请求已经发出,服务端确认资源未改
高频题标准答法
1. no-cache 和 no-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-Match、Last-Modified / If-Modified-Since。 - 静态资源通常:hash 文件名 + 长缓存;HTML 通常:短缓存或协商缓存。