打开 DevTools 的 Network 面板,会看到有些资源显示 200 (from disk cache),
有些显示 304 Not Modified。这两者都代表"没从服务器重新下载完整内容",
但背后的机制完全不同。理解它们,是配置好静态资源缓存策略的前提。
强缓存:本地直接用,不请求服务器
强缓存由响应头 Cache-Control 控制(旧规范里的 Expires 已基本被取代)。
命中时浏览器直接用本地副本,不发任何请求,因此 Network 面板里显示
from memory cache 或 from disk cache。
# 服务器响应头示例
Cache-Control: max-age=31536000 # 缓存 1 年(单位:秒)
Cache-Control: max-age=60, public # 缓存 1 分钟,允许所有中间缓存(CDN)使用
Cache-Control: no-cache # 每次都需向服务器确认(协商),但不代表"不缓存"
Cache-Control: no-store # 真正的不缓存:响应不落盘,每次全量下载
一个常见的误区:no-cache 字面意思是"不缓存",实际含义恰恰相反——
它允许缓存,但每次使用前都要向服务器验证(走协商缓存流程)。
真正禁止缓存的是 no-store。
协商缓存:问一下服务器,变了再下载
当强缓存失效(或响应头带了 no-cache)时,浏览器会带上上次的"凭证"去问服务器 "这个文件变了吗?"服务器返回 304 表示没变,浏览器继续用本地副本; 返回 200 + 新内容则表示变了。
凭证有两对:
-
ETag / If-None-Match(优先级更高): 服务器给内容生成一个指纹(如内容的 hash),浏览器下次请求时带上If-None-Match: "abc123",服务器比对后决定返回 304 还是 200; -
Last-Modified / If-Modified-Since: 基于文件最后修改时间,精度到秒,且可能因内容"改回去"而误判,因此现在一般优先用 ETag。
一次完整的缓存流程
第一次请求
浏览器 ──GET /app.js──▶ 服务器
浏览器 ◀─200 + Cache-Control: max-age=60 + ETag: "v1"── 服务器
浏览器 保存到本地缓存
第二次请求(60 秒内)
浏览器 ──直接使用本地副本──▶(无网络请求)→ 200 from disk cache
第三次请求(超过 60 秒)
浏览器 ──GET /app.js,If-None-Match: "v1"──▶ 服务器
浏览器 ◀─304 Not Modified(无 body)── 服务器
浏览器 继续使用本地副本
实践建议
-
带 hash 的静态资源(如
app.8f3a2b.js):内容变了文件名就变, 可以放心设置max-age=31536000, immutable,一年都不需要重新验证; -
HTML 文档:建议
no-cache,保证每次都能拿到最新的页面引用(进而拿到最新的资源 hash); -
API 响应:按业务决定,实时性要求高的数据用
no-store或短 max-age; -
CDN 配合:给静态资源加
public指令,让 CDN 节点也能缓存,源站压力更小。
总结:强缓存省请求,协商缓存省流量。把"带 hash 的资源长缓存、HTML 走协商"当成默认策略,就抓住了缓存的 80%。