引言
上线新功能后,满怀期待地刷新页面,结果看到的却是老界面。清空缓存、强制刷新甚至重启浏览器,用户依然在投诉'怎么没变化'。缓存,这个为了提升性能而生的机制,在代码更新时却成了最大的敌人。
今天结合实战经验,彻底讲透浏览器缓存机制,并给出一个能完美解决代码更新后缓存问题的终极方案。
浏览器缓存机制详解
浏览器缓存主要分为两类:强缓存和协商缓存。它们像两道关卡,决定了资源是从本地加载还是从服务器获取。
1. 强缓存(无需询问服务器)
强缓存是指浏览器在缓存有效期内,直接使用本地副本,不发送任何请求。它由以下两个 HTTP 响应头控制:
- Expires:HTTP/1.0 的产物,指定一个绝对的过期时间(如
Expires: Wed, 21 Oct 2025 07:28:00 GMT)。缺点是依赖客户端时间,容易出问题。 - Cache-Control:HTTP/1.1 的规范,优先级高于 Expires。常用指令有:
max-age=3600:资源在 3600 秒内有效。public:允许所有中间节点(如 CDN)缓存。private:只允许浏览器缓存。no-cache:不直接使用强缓存,但允许协商缓存。no-store:完全禁用缓存,每次都请求服务器。
当强缓存命中时,浏览器直接从磁盘或内存中读取资源,Network 面板显示 200 (from disk cache) 或 200 (from memory cache)。
2. 协商缓存(需要询问服务器)
当强缓存过期(或设置了 no-cache)时,浏览器会携带缓存的标识向服务器发起请求,由服务器判断资源是否更新。如果未更新,返回 304 状态码,告诉浏览器继续使用缓存;如果已更新,返回 200 和新资源。
协商缓存也由两组响应头/请求头控制:
- Last-Modified / If-Modified-Since:
- 服务器返回
Last-Modified: 文件最后修改时间。 - 下次请求时,浏览器带上
If-Modified-Since: 上次返回的时间。 - 服务器对比时间,如果文件未修改则返回 304。
- 缺点:时间精度只到秒,如果文件在 1 秒内多次修改,可能无法识别;且时间可能因代理服务器修改而失真。
- 服务器返回
- ETag / If-None-Match:
- 服务器根据文件内容生成唯一标识(如哈希值),返回
ETag: "xxxx"。 - 下次请求时,浏览器带上
If-None-Match: "xxxx"。 - 服务器对比 ETag,如果一致则返回 304。
- ETag 解决了 Last-Modified 的精度问题,但计算 ETag 会消耗服务器性能。
- 服务器根据文件内容生成唯一标识(如哈希值),返回
前端代码更新的缓存难题
我们的前端资源通常包括:HTML、JS、CSS、图片等。它们有不同的缓存策略需求:
- HTML:希望每次请求都获取最新版本,因为 HTML 里引用了其他资源的链接(如
<script src="app.js">)。 - JS/CSS/图片:希望长期缓存,提升性能,但当内容变化时,浏览器能加载新版本。
如果所有资源都设置长期缓存,那么代码更新后,用户访问页面时,HTML 可能还是旧的,引用的也是旧的 JS/CSS,导致新功能无法生效。这就是典型的'缓存更新问题'。
终极解决方案:基于文件内容的哈希命名
核心思想:让资源 URL 随内容变化而变化。这样,即使资源被长期缓存,当内容变化时,URL 也会变化,浏览器自然就会请求新资源。


