【缓存篇】强缓存与协商缓存:最快的请求是“不发请求”

9 阅读4分钟

在《前端视角下的网络协议》系列中,我们聊过了连接的开销和安全的代价。但正如标题所言,最快的请求就是“不发请求”

作为一名拥有 8 年经验的全栈开发者,你一定遇到过“代码改了用户却看不到更新”或“首屏加载全是 200 (from disk cache)”的情况。掌握浏览器的缓存协议,本质上是在**用户体验(速度)数据一致性(更新)**之间寻找那个完美的平衡点。


一、 缓存层级:资源到底存哪了?

当你打开控制台的 Network 面板,你会看到不同的缓存来源:

  1. Memory Cache(内存缓存): 响应最快,但生命周期短,关闭标签页即消失。通常存储脚本、字体、图片。
  2. Disk Cache(硬盘缓存): 响应稍慢,但容量大、持久化。大多数强缓存资源都存在这里。
  3. Service Worker Cache: 离线缓存的“黑盒”,完全由开发者通过代码控制,优先级最高。
  4. Push Cache: HTTP/2 的产物,生命周期极短(约 5 分钟),在 HTTP 会话结束时释放。

二、 强缓存:直接“篡改”浏览器的时钟

强缓存意味着浏览器完全不向服务器发送请求,直接从本地读取资源。状态码虽然显示为 200,但后面会标注 (from memory cache)(from disk cache)

1. Expires (HTTP/1.0)

使用绝对时间(如 Thu, 01 Dec 2026 16:00:00 GMT)。

  • 缺陷: 极度依赖客户端本地时间。如果用户手动改了电脑时钟,缓存就会失效或永远不过期。

2. Cache-Control (HTTP/1.1)

现代 Web 的标准,使用相对时间(秒),优先级高于 Expires

  • 常用指令:

    • max-age=31536000: 缓存一年(适合带 Hash 的静态资源)。
    • no-cache: 不是不缓存,而是跳过强缓存,直接进入“协商缓存”环节(必须先询问服务器)。
    • no-store: 真正的“不缓存”,不存入硬盘。
    • public/private: 是否允许中间代理(如 CDN)缓存。

三、 协商缓存:给服务器发个“确认函”

当强缓存失效(超过了 max-age),或者配置了 no-cache 时,浏览器会携带一些指纹信息去询问服务器:“我的这份资源还能用吗?”

1. Last-Modified / If-Modified-Since

  • 原理: 基于文件的最后修改时间。
  • 缺陷: 时间只能精确到秒。如果文件在一秒内修改多次,或者文件没变但修改时间变了(比如重新上传),缓存会产生误判。

2. Etag / If-None-Match

  • 原理: 服务器根据文件内容生成的唯一哈希值(指纹)。
  • 优势: 精度高,只要内容变了指纹一定变。优先级高于 Last-Modified
  • 流程: 如果指纹没变,服务器返回 304 Not Modified(报文体为空),浏览器继续使用本地旧资源。

四、 全栈视角:如何配置“生产环境级”的缓存?

作为资深开发者,你应该针对不同类型的资源制定不同的协议策略:

1. HTML 文件:永远的 no-cache

HTML 是应用的入口。如果强缓存了 HTML,你发布的 JS/CSS 更新将永远无法触达用户。

  • 策略: Cache-Control: no-cache。配合 Etag,让浏览器每次都来询问服务器。

2. 静态资源 (JS/CSS/Images):极致的“长缓存”

在 Vite/Webpack 构建时代,文件名里都带着 Content Hash。

  • 策略: Cache-Control: max-age=31536000, immutable
  • 原理: 只要文件名变了,就是新资源;只要文件名没变,一年内都不用发请求。

3. API 接口:按需选择

  • 配置数据: 可以设置较短的强缓存(如 5-10 分钟)。
  • 核心业务数据: Cache-Control: no-store,确保数据的绝对实时。

五、 总结:缓存决策树

  1. 资源是否需要缓存? 否 -> no-store
  2. 资源是否每次都要验证? 是 -> no-cache
  3. 是否允许代理/CDN 缓存? 是 -> public;否 -> private
  4. 资源有效期多久? 设置 max-age
  5. 强缓存失效后如何验证? 配置 Etag (优先) 或 Last-Modified

💡 结语

掌握缓存协议,是区分“会写业务的前端”与“懂工程的全栈”的分水岭。通过合理的强缓存与协商缓存组合,你可以让应用的重复访问速度提升一个量级,同时大幅减轻服务器负担。