在《前端视角下的网络协议》系列中,我们聊过了连接的开销和安全的代价。但正如标题所言,最快的请求就是“不发请求” 。
作为一名拥有 8 年经验的全栈开发者,你一定遇到过“代码改了用户却看不到更新”或“首屏加载全是 200 (from disk cache)”的情况。掌握浏览器的缓存协议,本质上是在**用户体验(速度)与数据一致性(更新)**之间寻找那个完美的平衡点。
一、 缓存层级:资源到底存哪了?
当你打开控制台的 Network 面板,你会看到不同的缓存来源:
- Memory Cache(内存缓存): 响应最快,但生命周期短,关闭标签页即消失。通常存储脚本、字体、图片。
- Disk Cache(硬盘缓存): 响应稍慢,但容量大、持久化。大多数强缓存资源都存在这里。
- Service Worker Cache: 离线缓存的“黑盒”,完全由开发者通过代码控制,优先级最高。
- 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,确保数据的绝对实时。
五、 总结:缓存决策树
- 资源是否需要缓存? 否 ->
no-store。 - 资源是否每次都要验证? 是 ->
no-cache。 - 是否允许代理/CDN 缓存? 是 ->
public;否 ->private。 - 资源有效期多久? 设置
max-age。 - 强缓存失效后如何验证? 配置
Etag(优先) 或Last-Modified。
💡 结语
掌握缓存协议,是区分“会写业务的前端”与“懂工程的全栈”的分水岭。通过合理的强缓存与协商缓存组合,你可以让应用的重复访问速度提升一个量级,同时大幅减轻服务器负担。