浏览器 HTTP 缓存:强缓存、协商缓存与「为什么改了代码用户还看到旧的」
知识背景
静态资源(JS/CSS/图片)和接口响应在到达页面之前,往往会经过浏览器缓存。缓存能减少重复下载、加快二次访问,但也带来经典问题:发版后用户仍加载旧文件。
在 Node 或 Nginx 上配置 Cache-Control、ETag 等头,会直接影响浏览器行为。前端工程里的「文件名带 hash」「HTML 不长期强缓存」等策略,都是为了和 HTTP 缓存规则打配合。
知识详解与通俗解释
1. 缓存大致存在哪儿?
- 内存缓存(Memory Cache):当前标签页会话内较快,刷新可能还在;容量小。
- 磁盘缓存(Disk Cache):落盘,跨会话;容量大。
- Service Worker 缓存:由 PWA/离线策略控制,优先级与规则更复杂,本文不展开。
可以粗记:浏览器会尽量「少联网、多复用」;是否复用由响应头 + 浏览器启发式共同决定。
2. 强缓存:在「过期前」不问服务器
关键响应头:
Cache-Control(现代首选):如max-age=31536000表示一年内直接用本地副本,不再发验证请求(在 max-age 有效期内)。no-store:完全不缓存。no-cache:容易误解——实际常配合协商用,表示使用缓存前必须先向服务器验证(见下)。private/public:是否允许共享缓存(如 CDN)缓存。
Expires:旧式绝对过期时间;与Cache-Control同时存在时,通常 以Cache-Control为准。
通俗理解:强缓存像「外卖放冰箱,保质期内不重新点单」;no-store 是「不存冰箱,每次都新买」。
3. 协商缓存:过期或 no-cache 时,问服务器「还能不能用」
浏览器会带条件头,服务器返回 304 Not Modified 则继续用磁盘/内存里的 body,节省带宽。
常见机制:
Last-Modified/If-Modified-Since:按文件修改时间。ETag/If-None-Match:按实体标签(常为内容哈希),更可靠(时间精度、多机部署等问题更少)。
通俗理解:协商缓存像「问问店家这份外卖还能不能吃,店家说能,就继续吃旧的;不能说就换新的」。
4. 与前端工程实践的关系
- 带 hash 的文件名(如
app.a1b2c3.js):内容变则 URL 变,天然绕过旧强缓存。 - 入口 HTML:通常不宜设超长
max-age,否则用户长期拿不到新引用。 - 接口 GET:是否缓存要看业务;API 常配
no-store或短max-age,避免脏读。
5. Node /静态服务里你在配什么?
用 express.static、Koa、或 Nginx expires 时,本质都是在写这些头。线上若「静态资源 304 过多」或「从不304」,都值得对照 Cache-Control与 ETag 配置查一遍。
总结
- 强缓存看
Cache-Control/Expires:未过期则可能不发网络请求(或仅从缓存取)。 - 协商缓存用
ETag/Last-Modified:省流量但仍有往返,适合可复用但可能更新的资源。 - 发版与缓存:靠「不可变资源 + hash」与「HTML 短缓存或不缓存」组合,比让用户清缓存更靠谱。