浏览器缓存与资源加载

第二次打开更快,主要不是网络变好了,而是资源直接从本地缓存读取。

同一个网页第二次打开明显更快,主要不是网络变好了,而是大部分资源直接从本地缓存读取,根本没有产生网络请求。理解缓存机制,能解释很多"为什么有时快有时慢"的现象。

缓存的两个层次

浏览器缓存主要分两类,工作方式完全不同:

强缓存

服务器在响应头里告诉浏览器"这个资源在某个时间之前不用再来问"。浏览器把这个规则记下来,在有效期内直接使用本地副本,完全不发请求。这是最快的情况——耗时接近于零。

控制强缓存的响应头有两个:Cache-Control: max-age=3600 表示 3600 秒内有效;Expires 是旧式写法,指定一个绝对时间点。现代站点优先使用 Cache-Control。

协商缓存

强缓存过期后,浏览器会向服务器发一个"问询"请求:我本地这份是某个版本,还能用吗?服务器回答"可以"(返回 304,无内容)或"不行,用新的"(返回 200 和新内容)。

协商缓存的判断依据有两个:ETag(内容指纹)和 Last-Modified(最后修改时间)。用 ETag 更准确,因为时间戳可能因为部署方式变化而失真。

协商缓存的往返仍然需要一次网络请求,但节省了传输内容的开销。所以它的速度介于"强缓存命中"和"完全重新下载"之间。

资源指纹的作用

强缓存有一个固有矛盾:缓存时间设得太短,每次都要重新验证,起不到加速作用;设得太长,内容更新后用户还在用旧版本,出问题。

解决办法是给文件名加指纹。比如把 app.js 改成 app.a1b2c3.js,其中 a1b2c3 是文件内容的哈希值。这样:

  • 内容不变 → 文件名不变 → 可以设置极长的缓存时间(比如一年)
  • 内容改变 → 文件名改变 → 浏览器视为全新资源,自动重新下载

这就是为什么很多站点的资源文件名里带一串看不懂的字符。它不是随机数,而是内容的指纹。这套机制让"永久缓存"和"即时更新"可以同时成立。

但有个前提:HTML 文件本身不能设长缓存。因为 HTML 里引用了哪些资源文件名是变化的,如果 HTML 被缓存了,浏览器就永远不知道资源换了名字。所以标准做法是 HTML 用短缓存或不缓存,资源文件用超长缓存。

缓存导致的常见问题

缓存虽然加速,但也制造了一类典型故障:更新后部分用户看到旧内容。原因通常是某一层缓存没刷新。可能的位置包括:浏览器本地缓存、CDN 边缘节点缓存、反向代理缓存。

排查方法是看响应头里的标记:X-Cache、CF-Cache-Status、Age 等字段会说明这次响应来自缓存还是源站。如果命中缓存但内容应该是新的,就需要在对应层级做刷新。

另一种常见情况是缓存了不该缓存的内容。比如按用户区分的页面被 CDN 当成公共资源缓存,导致 A 用户看到 B 用户的内容。这类问题通常通过设置 Cache-Control: private 或者按 Cookie 分流来解决。

常见疑问

问:清除缓存会不会丢数据?

答:清除"缓存"通常只影响临时文件,不丢登录状态。但清除"网站数据"或"Cookie"会丢失登录状态,因为那些不是缓存而是本地存储。两者在设置界面里常常被放在一起,需要注意区分。

问:为什么手机上清了缓存还是旧页面?

答:可能中间还有一层 CDN 缓存,或者页面被 Service Worker 接管了。后者需要在开发者工具里单独注销 Service Worker 才能清除。

问:无痕模式有缓存吗?

答:有,但关闭窗口时清除。所以无痕模式下第一次访问同样慢,刷新之后会变快,只是这个缓存活不过本次会话。

进入体验入口

浏览器直接打开,无需下载安装