Skip to content

浏览器缓存机制

浏览器缓存(Browser Caching)是 Web 性能优化中最重要、最常用的一环。它的核心思想是:**将服务器返回的静态资源(如图片、CSS、JS 文件)暂时保存在用户本地磁盘或内存中,当下次请求同一个资源时,直接从本地读取,而不必再向服务器发起完整的请求。**这能大幅减少网络延迟、降低服务器压力,并极大地提升网页的加载速度和用户体验。

浏览器缓存机制主要分为两类:强缓存(强制缓存)协商缓存(对比缓存)

1. 强缓存 (Strong Cache)

强缓存是最高效的缓存方式。当浏览器判断命中强缓存时,完全不会向服务器发送任何网络请求,而是直接从本地(Memory Cache 或 Disk Cache)读取资源。

在浏览器的 Network 面板中,你会看到状态码为 200 OK (from memory cache)200 OK (from disk cache)

强缓存通过 HTTP 响应头中的两个字段来控制:ExpiresCache-Control

1.1 Expires (HTTP/1.0 产物)

  • 作用:服务器返回一个绝对的过期时间(GMT 格式的时间戳)。在此时刻之前,浏览器都会直接使用本地缓存。
  • 示例Expires: Wed, 21 Oct 2026 07:28:00 GMT
  • 致命缺陷:它依赖于客户端(用户电脑)的系统时间。如果用户的本地时间与服务器时间不一致,或者用户手动修改了本地时间,那么缓存的判断就会完全失效。因此,它现在基本上被淘汰了,仅作为向后兼容的保留手段。

1.2 Cache-Control (HTTP/1.1 标准,目前的主流)

  • 作用:为了克服 Expires 的缺陷,Cache-Control 使用相对时间来控制缓存。
  • 最高优先级:如果同时存在 ExpiresCache-Control,浏览器会优先遵循 Cache-Control
  • 常见指令
    • max-age=<seconds>:最重要的指令。表示资源从被请求的那一刻起,在指定的秒数内有效。例如 Cache-Control: max-age=31536000 表示缓存一年。在这期间内,直接走强缓存。
    • no-cache(容易误解)它的意思不是“不缓存”,而是“可以缓存,但在每次使用前,必须向服务器发送请求,进行协商缓存的验证”。
    • no-store:这才是真正的“绝对不缓存”。浏览器和任何中间代理都不允许存储这个响应的任何内容。每次必须向服务器发完整请求。
    • public:表示该响应可以被任何对象(包括客户端浏览器、CDN 节点、代理服务器等)缓存。
    • private:表示该响应是针对单个用户的,只能被客户端浏览器缓存,中间的代理服务器(CDN)不能缓存。这是默认值。

2. 协商缓存 (Negotiation Cache)

强缓存失效(即没有设置强缓存,或者 max-age 已过期,或者设置了 no-cache),浏览器就会向服务器发起请求,验证本地缓存的内容是否仍然是最新版本。这个验证过程就是协商缓存。

协商缓存需要客户端和服务器互相配合,通过两对特殊的 HTTP 头部字段来实现。

2.1 Last-Modified / If-Modified-Since (基于修改时间)

  1. 初次请求:服务器在返回资源时,会在响应头中加上 Last-Modified,表示这个资源在服务器上的最后修改时间
    • Last-Modified: Fri, 12 Feb 2026 15:00:00 GMT
  2. 再次请求:当缓存过期,浏览器再次请求该资源时,会在请求头中自动加上 If-Modified-Since,其值就是上次收到的 Last-Modified 的值。
  3. 服务器比对:服务器收到请求后,拿 If-Modified-Since 的时间与该资源在服务器上当前的最后修改时间进行比对。
    • 如果没有修改:服务器返回状态码 304 Not Modified,且不返回资源正文(Body 为空)。这告诉浏览器:“资源没变,你继续用你本地的旧缓存吧。”(这就是协商缓存命中的表现)。
    • 如果已修改:服务器返回状态码 200 OK,并带上最新的资源正文和新的 Last-Modified 时间。
  • 缺陷
    • Last-Modified 的时间精度只能精确到。如果一个文件在 1 秒内被修改了多次,它无法察觉。
    • 如果某些文件被定时打开并重新保存,但内容根本没变Last-Modified 时间也会更新,导致本可重用的缓存失效,造成不必要的重新下载。

2.2 ETag / If-None-Match (基于文件内容的唯一标识)

为了解决时间精度的缺陷,HTTP/1.1 引入了 ETag。

  1. 初次请求:服务器在返回资源时,根据资源的内容生成一个唯一标识符(通常是一段哈希值/指纹),放在响应头的 ETag 字段中。只要文件内容改变,ETag 就会改变。
    • ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
  2. 再次请求:浏览器再次请求时,会在请求头中加上 If-None-Match,其值就是上次收到的 ETag
  3. 服务器比对:服务器拿 If-None-Match 的值与资源当前的 ETag 进行严格的字符串比对。
    • 如果匹配(说明内容没变):返回 304 Not Modified,浏览器继续用缓存。
    • 如果不匹配(说明内容变了):返回 200 OK 和新内容、新 ETag。
  • 最高优先级:在协商缓存中,ETag 的优先级高于 Last-Modified。如果同时存在,服务器会优先验证 ETag。

3. 缓存机制的完整流程梳理

  1. 浏览器尝试发起一个 HTTP 请求。
  2. 检查强缓存:浏览器查找本地是否存在该资源的缓存记录,并检查 Cache-Control (如 max-age) 判断是否过期。
    • 未过期:命中强缓存,直接从本地读取并返回 200 (from cache)。请求结束。
    • 已过期(或存在 no-cache:强缓存失效,准备发起网络请求进行协商缓存
  3. 准备协商请求:浏览器提取之前保存的 ETagLast-Modified,并将它们分别赋值给请求头 If-None-MatchIf-Modified-Since,发送给服务器。
  4. 服务器验证(协商缓存)
    • 服务器优先比对 ETag,再比对 Last-Modified
    • 未修改:服务器认为客户端的缓存仍然可用,返回 304 Not Modified,只带头部,不带 Body。浏览器收到 304 后,更新本地缓存的过期时间,并从本地加载资源。请求结束。
    • 已修改(或服务器没有这两个验证头):缓存失效,服务器返回 200 OK,附带全新的资源数据以及新的缓存头信息。浏览器将新资源存入缓存。请求结束。

Logo

4. 常见问题 (FAQ)

4.1 用户操作对缓存有什么影响?

  • 地址栏输入 URL 回车 / 点击书签 / 点击超链接:这是最正常的用户行为,浏览器会严格按照上述**完整的缓存机制(强缓存优先,失效再协商)**来执行。
  • 按 F5 刷新页面 / 点击浏览器刷新按钮:浏览器认为用户希望看到稍微新一点的内容。它会强制忽略所有强缓存(即便 max-age 没到期),向服务器发送请求。但在请求中依然会带上 If-None-Match/If-Modified-Since,也就是只进行协商缓存。如果资源没变,服务器依然返回 304。
  • 按 Ctrl + F5 强制刷新 (Hard Reload):浏览器认为用户遇到了严重问题,必须获取绝对最新的版本。浏览器会同时忽略强缓存和协商缓存。它在发送请求时会带上 Cache-Control: no-cache 并且丢弃验证头(不带 If-None-Match),迫使服务器必须返回 200 OK 和完整的最新资源。

4.2 from memory cachefrom disk cache 的区别是什么?

  • Memory Cache (内存缓存):读取速度极快,但持续性很短。通常用于缓存正在当前网页上使用的资源(如网页中加载好的多张图片、已经执行过的 JS/CSS 脚本)。一旦你关闭了这个浏览器的 Tab 标签页,内存缓存就会被清空。
  • Disk Cache (磁盘缓存):读取速度稍慢(因为需要读写硬盘),但容量大,且可以持久保存。哪怕你重启了电脑,只要缓存没过期,依然可以从磁盘中读取。大文件或者很少被修改的文件通常会被放入磁盘缓存。
  • 分配策略:浏览器有自己复杂的启发式算法来决定放哪里。一般来说,小的、频繁使用的资源放内存;大的、长期有效的放磁盘。

4.3 如何配置才能让前端发版(更新代码)后,用户立刻能看到最新效果,而不用手动清缓存?

这是前端工程化中最经典的问题。最佳实践是 “长缓存时间 + 文件名哈希 (Hash)” 组合拳:

  1. 对入口 HTML 文件:通常配置不进行强缓存,每次都必须验证(Cache-Control: no-cache)甚至不缓存(no-store)。因为 HTML 文件的内容很少,每次下载不耗时,它是后续所有资源加载的起点。
  2. 对静态资源 (JS/CSS/图片等):在构建工具(如 Webpack/Vite)打包时,在文件名中加入基于内容的 Hash 值(例如 app.v2a3bf1.js)。
  3. 配置强缓存:在服务器上为这些带 Hash 的静态资源配置超长的强缓存时间(例如一年:Cache-Control: max-age=31536000, immutable)。

原理

  • 只要你没改代码,文件名就不会变,用户访问时直接命中本地强缓存,速度飞快。
  • 当你修改了某行代码并重新打包发版后,该文件的 Hash 值发生变化,生成了一个全新的文件名(如 app.v8c9d0f.js)。
  • 用户再次访问时,浏览器请求入口 HTML 文件(HTML 不走强缓存,总能拉到最新版)。
  • 最新的 HTML 文件里引用了新的 JS 文件名。由于这是一个浏览器从未见过的全新 URL,它根本没有本地缓存,只能老老实实去服务器下载最新代码。从而完美解决了缓存更新问题。

4.4 为什么有了 ETag 还要保留 Last-Modified?

  • 生成 ETag 通常需要读取文件内容并计算哈希值,这是一个消耗服务器 CPU 性能的操作,特别是对于体积很大的文件。
  • 如果服务器判断出读取 Last-Modified 就能满足大部分场景的需求,可以节省计算 ETag 的开销。它们通常是搭配使用的,互为补充。

4.5 Cache-Control: no-cacheCache-Control: no-store 的区别是什么?

  • no-cache:代表客户端可以缓存资源,但在使用缓存前,必须向服务器发送请求进行验证(即走协商缓存路线)。如果服务器回复 304,就使用缓存;如果回复 200,就用新的。
  • no-store:代表绝对禁止任何形式的缓存(包括浏览器、中间代理服务器、CDN 等)。每次请求都必须像第一次那样,从服务器拉取完整的资源。通常用于极度敏感的数据(如银行流水页面)。