Appearance
把所有关键响应头分门别类讲清楚
本节目标:理解“把所有关键响应头分门别类讲清楚”的核心思路,并能把它用于实际开发或面试表达。 一、控制“能不能存、能存多久”的响应头Cache-Control: max-age=N 这是浏览器私有缓存最核心的 freshness 指令。响应年龄超过 N 秒后,就变 stale。对浏览器本地缓存来说,它通常是第一优先级。(MDN Web Docs)
Cache-Control: no-store 表示任何缓存都不应存储这个响应,浏览器私有缓存和共享缓存都不该留。它是“彻底不存”,不是“先存后校验”。(MDN Web Docs)
Cache-Control: no-cache 这是最容易误解的一个。它不是“不缓存”,而是“可以存,但每次复用前都必须去源站验证”。所以你经常会看到有缓存条目,但仍然每次都发条件请求。(MDN Web Docs)
Cache-Control: must-revalidate 它和 no-cache 不一样。must-revalidate 的含义是:fresh 时可直接用;一旦 stale,必须验证后才能继续复用。 它通常搭配 max-age 一起用。(MDN Web Docs)
Cache-Control: private 表示只允许私有缓存存,比如浏览器本地缓存;共享缓存不该存。个性化页面、登录后的页面最常见。(MDN Web Docs)
Cache-Control: public 它的主要意义是允许共享缓存去存;对浏览器本地缓存本身没有什么特殊“加速开关”的作用。尤其带 Authorization 的响应,默认不该被共享缓存存,但加了 public 会改变这一点。这个指令更多是在 CDN / 代理层有意义。(MDN Web Docs)
Cache-Control: immutable 表示只要这个响应还在 fresh 时间内,就认为它内容不会变。浏览器因此可以避免在刷新时对这类资源再发不必要的条件请求。最适合带内容 hash 的静态资源。(MDN Web Docs)
Cache-Control: stale-while-revalidate 表示响应在 fresh 窗口过后,还可以在额外的一段时间里先返回 stale 内容,同时在后台做 revalidate。这个机制的重点是“用户先拿旧内容,验证在后台”。(MDN Web Docs)
Cache-Control: s-maxage 这是共享缓存专用的 fresh 时间,浏览器私有缓存会忽略它。你看浏览器行为时,别把它当成浏览器本地缓存的 TTL。(MDN Web Docs)
Cache-Control: proxy-revalidate 这也是共享缓存专用,浏览器私有缓存不用它。(MDN Web Docs)
Cache-Control: must-understand 这是一个很少见的高级指令,用来要求缓存必须理解某些状态码对应的缓存语义,否则不要存。它不是日常前端配置主角。(RFC Editor)
Expires 老式的过期时间头。现在仍然有效,但只在没有 Cache-Control: max-age 时起作用;只要两者同时出现,max-age 优先。(RFC Editor)
Age 它不是“控制缓存”的开关,而是告诉接收方:“这份响应已经在缓存链路里老了多少秒”。浏览器会把它纳入年龄计算,因此它会影响还剩多少 fresh 时间。Age 的存在也说明响应不是针对这次请求刚由源站生成或验证出来的;但没有 Age,不代表一定联系了源站。(RFC Editor)
Date 它也不是缓存开关,但 freshness 计算离不开它。浏览器会把响应产生时间、Age、接收时间组合起来算年龄和剩余寿命。(RFC Editor)
二、控制“如何重验证”的响应头ETag ETag 是最重要的内容验证器。浏览器缓存条目过期、或者请求侧要求 revalidate 时,通常会把它带到 If-None-Match 里去问服务端:“我这份还是不是最新的?” 服务端如果认为还是同一个实体,就回 304。(MDN Web Docs)
Last-Modified 这是时间型验证器。它不如 ETag 精确,但可以作为很好的后备方案。浏览器会在适当时机把它放到 If-Modified-Since。规范和 MDN 都建议:如果可能,ETag 和 Last-Modified 都发。(MDN Web Docs)
三、控制“缓存 key 怎么算”的响应头Vary Vary 会把请求头并入缓存 key。最常见的场景是:
- Vary: Accept-Encoding
- Vary: Accept-Language
- Vary: Origin
只要响应内容会因为某个请求头不同而不同,就应该把那个请求头写进 Vary;否则浏览器或中间缓存可能把“给 A 的版本”错复用给“给 B 的请求”。另外,304 响应也应该保持同样的 Vary 值。(MDN Web Docs)
No-Vary-Search(实验性) 这是比较新的、实验性的响应头。它允许服务端声明:某些 query 参数不该参与缓存匹配,或者 key 顺序不重要。它不是你现在必须依赖的主流方案,但它确实已经属于“会影响浏览器缓存匹配”的机制。(MDN Web Docs)
四、控制“直接清空本地缓存”的响应头Clear-Site-Data: "cache" 这个头不是让当前响应“可缓存”,而是告诉浏览器:把这个站点相关的本地缓存清掉。 MDN 还特别提到,具体浏览器实现里,它可能连预渲染页、bfcache、脚本缓存等一起清。(MDN Web Docs)
第四步:所有关键请求头,也按作用讲清楚一、浏览器自动带上的验证类请求头If-None-Match 来源于之前响应里的 ETag。典型触发条件:
- 本地缓存条目 stale;
- 或请求端明确要求 revalidate,比如 cache: "no-cache";
- 或浏览器刷新时主动要求重验证。
如果服务端返回 304,浏览器继续使用缓存实体。(MDN Web Docs) If-Modified-Since 来源于之前响应里的 Last-Modified。常见于没有 ETag 的场景;若同时有 If-None-Match 和 If-Modified-Since,前者优先。(MDN Web Docs)
二、请求侧主动改写缓存行为的请求头Cache-Control: no-cache 请求头版本的 no-cache 表示:即使你本地有 fresh 缓存,也请先去验证一下再复用。 浏览器强制刷新时通常会加它。(MDN Web Docs) Cache-Control: max-age=0 这是浏览器“刷新”时非常常见的历史兼容写法,本质上也是在表达“我要最新的,不要盲目信 fresh 缓存”。很多浏览器普通 reload 仍会带它。(MDN Web Docs) Cache-Control: no-store 请求头版本表示:这次请求不要从缓存取,也不要把结果存入缓存。fetch(..., { cache: "no-store" }) 会落到这一类行为。(MDN Web Docs) Pragma: no-cache 这是老式、兼容性导向的请求头,效果可理解为 Cache-Control: no-cache。现在更多是历史兼容或某些工具链/浏览器行为里仍能见到。(MDN Web Docs) Cache-Control: only-if-cached 表示“只接受缓存结果,没有缓存就别去网络”。在 Fetch API 里,它只能用于 same-origin。(MDN Web Docs) Cache-Control: max-stale / min-fresh / stale-if-error 这些都是请求端理论上能表达的缓存偏好,但 MDN 明确提到:主流浏览器对 max-stale、min-fresh、请求头版本的 stale-if-error 支持很弱或没有支持,所以实际前端排查时很少把它们当主线。(MDN Web Docs)
