清空缓存?你永远不知道浏览器在背后干了什么

12 阅读7分钟

引言

“我明明已经部署了最新版本,为什么用户看到的还是旧页面?”

凌晨两点,运维小哥打电话给我的时候,声音里带着一丝绝望。就在十分钟前,我们刚修复了一个线上紧急 Bug,紧急上线,紧急通知所有用户强制刷新。然而,群里依然有人截图反馈:“这个 Bug 还在啊!”

我打开自己电脑的隐身模式,访问页面——一切正常。于是我得出一个自信的结论:“用户肯定没刷新。”

直到我亲自跑到产品经理的电脑前,看着她疯狂按 Cmd + R 至少五遍,页面还是老样子。我颤颤巍巍地打开 DevTools 的 Network 面板,发现所有请求都写着同一个单词:(disk cache)

那一瞬间我明白了:浏览器才是真正的“时间旅行者”——它把旧代码藏在硬盘里,不管你怎么刷新,它都坚持给你看“过去”。

今天,我们就来揭开浏览器缓存的神秘面纱。它到底是我们的好帮手,还是偷偷捣乱的“顽固分子”?

一、缓存不是 bug,是特性(但别让它变成特性)

浏览器缓存的设计初衷是好的:减少网络请求,加快页面加载,节约流量。但当我们更新代码后,它就成了最大的“拦路虎”。

缓存主要分两种:强缓存协商缓存

1.1 强缓存:浏览器“死心眼”模式

强缓存指的是浏览器在缓存有效期内,根本不向服务器发请求,直接使用本地副本。判断依据是响应头里的两个字段:

  • Expires:一个绝对时间(HTTP/1.0),比如 Expires: Wed, 22 Mar 2026 10:00:00 GMT。在这个时间之前,浏览器都不会重新请求。
  • Cache-Control:HTTP/1.1 引入的更强大的控制,比如 Cache-Control: max-age=3600,表示资源在 3600 秒内直接使用缓存。

如果两者同时存在,Cache-Control 优先级更高。

强缓存生效时,Network 面板里会看到状态码 200,但 Size 一栏会显示 (memory cache)(disk cache),意味着根本没走网络。

1.2 协商缓存:跟服务器“商量一下”

协商缓存是浏览器先去服务器问问:“我这个缓存还能用吗?”如果服务器说能用(304),浏览器就用缓存;如果说不能,就返回新内容(200)。

协商缓存主要通过两对头实现:

  • Last-Modified / If-Modified-Since:服务器返回资源时带上 Last-Modified(最后修改时间)。下次请求时,浏览器带上 If-Modified-Since: 上次修改时间。服务器对比时间,若未修改则返回 304。
  • ETag / If-None-Match:ETag 是资源的唯一标识(通常是内容哈希)。浏览器下次请求时带上 If-None-Match: 之前的ETag,服务器比对 ETag 是否一致,决定返回 304 还是新内容。

ETag 比 Last-Modified 更精确,因为时间可能变化但内容未变,或者时间未变但内容变了(比如文件被覆盖)。

二、为什么你明明更新了,用户还是旧的?

常见的坑有这几个:

坑1:HTML 被强缓存了

如果你的 index.html 被设置了 Cache-Control: max-age=3600,那么用户在这一小时内访问,浏览器都不会去服务器拿新的 HTML。而 HTML 里引用的 JS、CSS 文件又用了带哈希的文件名(如 main.a1b2c3.js),那么即使 JS 更新了,用户拿到的还是旧的 HTML,旧 HTML 里引用的还是旧的 JS 路径。

解决方案:HTML 文件绝对不能强缓存,通常设置为 Cache-Control: no-cache(每次都协商缓存)或者 Cache-Control: max-age=0, must-revalidate。同时,JS、CSS 等资源使用文件名哈希(content hash),内容变化时文件名变化,这样它们可以被强缓存,但版本更新时自动失效。

坑2:服务器时间不对

ExpiresLast-Modified 依赖服务器时间。如果服务器时间不准,可能导致缓存提前失效或过期不更新。

解决方案:尽量使用 Cache-Control: max-age 替代 Expires,因为它不依赖绝对时间。

坑3:CDN 缓存没刷新

即使你的源服务器更新了,CDN 节点可能还缓存着旧资源。很多 CDN 需要手动刷新或设置合理的缓存策略。

解决方案:部署时自动调用 CDN 刷新 API,或者给资源文件名加上 hash,这样新资源 URL 不同,CDN 自然就会回源。

坑4:用户侧强制刷新真的“强制”吗?

用户按 Cmd + R(或 F5)时,浏览器会带上 Cache-Control: max-age=0,告诉服务器“我不想要强缓存”,但协商缓存仍然可能生效。如果服务器返回 304,用户看到的还是旧资源。

真正的“硬刷新”是 Cmd + Shift + R(或 Ctrl + F5),它会忽略所有缓存,强制请求新资源。但你不能指望用户每次都知道这个操作。

三、实战:如何设计一个“安全”的缓存策略

一个经典的、经过无数大厂验证的缓存方案是:

  1. HTML 文件Cache-Control: no-cache(每次都向服务器验证,但服务器可以返回 304 节省带宽)。或者直接 Cache-Control: no-store(完全不缓存),但会损失性能。
  2. CSS、JS、图片等静态资源Cache-Control: max-age=31536000, immutable(一年强缓存),配合文件名哈希(如 main.9a8b7c.js)。内容一变,文件名变,用户自然请求新资源。
  3. 字体文件:同静态资源。
  4. API 数据:根据业务决定,通常 Cache-Control: no-cache 或短时间 max-age=60

这样既保证了更新能及时生效,又最大化利用缓存性能。

四、如何验证缓存是否生效?

  • 打开 Chrome DevTools → Network 面板。
  • 刷新页面,看某个资源的 Size 列:如果是 (memory cache)(disk cache),说明命中了强缓存。如果状态码是 304,说明命中了协商缓存。
  • 勾选 Disable cache 可以临时关闭所有缓存,方便调试(仅对当前 DevTools 生效)。
  • 查看响应头:Cache-ControlExpiresLast-ModifiedETag 是否按预期设置。

五、那些年我们被缓存坑过的往事

场景一:修改了 CSS,上线后发现样式没变。打开 Network 一看,CSS 文件是 (disk cache),原因是文件名没变,且 Cache-Control: max-age=604800。从此学会给文件名加 hash。

场景二:部署了新版,但某个老用户反馈页面错乱。排查发现他的浏览器一直缓存的旧 HTML 里引用了已删除的旧 JS 文件。从那以后,我强制要求 HTML 不得强缓存。

场景三:公司官网换了个 Logo,CDN 上的图片却还是旧的。最后发现 CDN 缓存了 7 天,而运维没做刷新。解决方案:部署时自动清除 CDN 缓存。

六、总结:与缓存做朋友,而不是敌人

浏览器缓存不是恶魔,它是我们优化性能的利器。只是我们需要理解它的脾气,用正确的姿势驯服它:

  • 静态资源用内容哈希 + 长期强缓存。
  • 入口 HTML 用协商缓存不缓存
  • 重要更新时,确保文件名变化,而不是依赖用户手动刷新。
  • 善用 Chrome DevTools 验证缓存策略。

下次当你更新代码后,发现用户还在用旧版本,先别急着骂“他们不刷新”——很可能是你的缓存策略在偷偷作祟。


每日一问:你在项目里遇到过最奇葩的缓存问题是什么?是图片死活不更新,还是 API 数据被 CDN 缓存了三天?评论区聊聊你的“缓存历险记”!


(本文所述故事均为真实改编,如有雷同,说明你也该检查一下你的 Cache-Control 了。)