cache-control 优先级永远高于 expires,只要 cache-control 存在(如 no-cache、max-age=0 等),浏览器就忽略 expires 字段,严格按 cache-control 指令执行缓存策略。

Cache-Control 和 Expires 冲突时谁生效?
Cache-Control 优先级永远高于 Expires,无论时间戳设得多远、多近。浏览器在判断强制缓存是否命中时,只要 Cache-Control 存在(哪怕只有 no-cache),就直接忽略 Expires 字段。
常见错误现象:
- 后端同时设置了
Expires: Fri, 31 Dec 9999 23:59:59 GMT和Cache-Control: no-cache,结果资源每次刷新都发请求——不是缓存没起作用,是no-cache主动禁用了“无协商直取”,强制走协商流程; - 开发环境用代理工具临时修改了
Expires,但忘了删Cache-Control: max-age=0,导致修改无效。
实操建议:
- 生产环境尽量只用
Cache-Control,避免和Expires并存造成理解混乱; - 调试时用浏览器 DevTools 的 Network 面板,看响应头里两个字段是否都存在,再对照状态码(
200 (from memory cache)vs304vs200)反推实际生效策略; -
max-age=0和no-cache表面相似,但语义不同:max-age=0允许缓存、只是立即过期,仍会带If-None-Match发协商请求;no-cache是“可存,但每次都要验证”,行为一致,但更明确。
为什么加了 ETag 还是没返回 304?
ETag 不是“开了就自动生效”的开关,它需要请求头和响应头严格配对,且服务端必须做比对逻辑。
常见错误现象:
- 服务器返回了
ETag: "abc123",但客户端第二次请求没带If-None-Match: "abc123"—— 多半是前端发请求时用了cache: 'no-store'或手动清除了 headers; - 服务端收到
If-None-Match,但代码里压根没读这个 header,或者比对逻辑写成if (etag !== req.headers['if-none-match'])却忽略了引号(真实值是"abc123",而你比的是abc123); - CDN 或反向代理(如 Nginx)默认不透传
If-None-Match,或自己生成了新的 ETag 覆盖了源站的。
实操建议:
- 用 curl 验证最可靠:
curl -I -H 'If-None-Match: "abc123"' https://example.com/asset.js,看返回是200还是304; - Node.js/Koa 中,别依赖框架自动处理 ETag,显式检查
req.headers['if-none-match']并手动res.status(304).end(); - 静态文件服务(如
koa-static)默认开启 ETag,但若文件内容不变却 ETag 总变,可能是启用了weak ETag(W/"abc123")且底层 fs.stat 时间精度干扰了生成逻辑。
no-cache 和 no-store 容易混淆的点
no-cache 不等于“不缓存”,no-store 才是真正禁止任何本地存储。面试常考这点,也是线上最容易配错的地方。
使用场景差异:
-
no-cache:适合用户仪表盘数据,内容更新频繁但体积小,允许浏览器存副本,每次用前和服务端确认是否变更; -
no-store:适合密码修改页、支付结果页,敏感信息绝不落地,连内存都不许存; - 混用风险:比如配置了
Cache-Control: no-cache, private, max-age=3600——no-cache和max-age逻辑冲突,浏览器以no-cache为准,max-age形同虚设。
性能影响:
-
no-cache仍会触发一次 HTTP 请求(带If-None-Match或If-Modified-Since),服务端需做比对,有计算开销; -
no-store看似安全,但反复加载同一张图片或 JS,会显著增加首屏时间与流量消耗,不该滥用。
强缓存失效后,为什么没走协商缓存而是直接 200?
这不是协商缓存失效,而是根本没触发协商——浏览器跳过了“发条件请求”这步,直接当全新请求处理了。
关键原因:
- 资源被标记为
private且请求是跨域的,某些浏览器(尤其旧版 Safari)会拒绝复用协商缓存; - 页面通过
location.reload(true)强制刷新,等价于加了Cache-Control: no-cache请求头,跳过所有缓存路径; - DevTools 勾选了 “Disable cache”,此时所有请求无视响应头里的缓存指令;
- 服务端返回了
Cache-Control: must-revalidate,但没配 ETag 或 Last-Modified,导致协商无法进行,只能退化为完整响应。
排查要点:
- 先关掉 DevTools 的 Disable cache 选项;
- 检查 Network 面板中该请求的 Initiator 列,如果是
reload或javascript,说明是脚本主动触发的非标准请求; - 确认响应头中是否同时存在
ETag和Last-Modified—— 二者只需其一,但若都缺失,协商缓存就无法成立。
304 响应,或前端无意中切断了协商链路。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











