浏览器缓存

116 阅读9分钟

核心目标:  浏览器缓存的核心目标是减少网络请求的数量和体积,从而:

  1. 提升页面加载速度:从本地读取资源远比从网络下载快得多。
  2. 降低服务器压力:减少不必要的请求。
  3. 节省用户流量:避免重复下载相同资源。

浏览器缓存策略概览:

当浏览器需要请求一个资源(如HTML、CSS、JS、图片)时,它会按照以下流程决策:

  1. 检查强缓存:  首先检查这个资源是否在本地缓存中并且是否过期

    • 如果有效(未过期) :浏览器直接使用本地缓存的资源完全不发送任何HTTP请求到服务器。状态码表现为 200 OK (from disk cache) 或 200 OK (from memory cache)
    • 如果无效(已过期) :则进入下一步 - 协商缓存
  2. 进行协商缓存:  浏览器知道本地有缓存,但不确定是否最新。它会携带特定的验证信息(缓存标识)向服务器发起一个请求

    • 如果服务器判断资源未修改:返回 304 Not Modified 状态码,不返回资源内容。浏览器收到304后,直接使用本地缓存的资源
    • 如果服务器判断资源已修改:返回 200 OK 状态码,并返回最新的资源内容和新的缓存标识。浏览器使用新资源并更新本地缓存。

现在,我们深入强缓存和协商缓存:

一、强缓存 (Strong Cache / Local Cache)

  • 核心思想:  完全不与服务器通信。浏览器根据自己的缓存规则,判断资源是否在有效期内。如果在,就直接用。

  • 控制字段:  主要通过HTTP响应头设置:

    1. Expires (HTTP/1.0)

      • 值是一个绝对时间 (GMT格式),例如 Expires: Wed, 25 Jul 2025 08:00:00 GMT
      • 缺点:依赖客户端时间。如果用户修改了本地时间,会导致缓存判断错误。
      • 优先级低于 Cache-Control
    2. Cache-Control (HTTP/1.1)

      • 值是一个或多个指令,更灵活、更常用,优先级更高。

      • 关键指令:

        • max-age=: 资源的最大有效时间(单位:秒)。例如 Cache-Control: max-age=3600 表示资源在1小时内有效。这是一个相对时间,从资源被请求成功的时间开始计算,不依赖客户端时间。
        • public:响应可以被任何对象(客户端、代理服务器等)缓存。
        • private:响应只能被单个用户(浏览器)缓存,不能被代理服务器等共享缓存。
        • no-cache不使用强缓存。每次使用缓存前必须向服务器发起协商验证请求(走协商缓存流程)。名字有点误导,它实际是“跳过强缓存,进入协商缓存”。
        • no-store完全不缓存。每次请求都直接从服务器获取完整响应。最严格的策略。
        • immutable (非标准但广泛支持):表示资源永不过期(在用户主动刷新前)。适用于带指纹(hash)的资源,明确告诉浏览器这个文件内容永远不会变,即使强制刷新(F5)也优先使用缓存(需要Ctrl+F5或清除缓存才能更新)。提升刷新体验。
        • s-maxage=:针对共享缓存(如CDN、代理服务器)设置的有效期,优先级高于 max-age
  • 强缓存生效时的表现:

    • Chrome DevTools Network Tab 中:状态码显示 200 OK,并在Size列显示 (disk cache) 或 (memory cache)
    • 没有网络请求发出!  这是性能提升的关键。

二、协商缓存 (Conditional Cache / Validation Cache)

  • 核心思想:  需要与服务器通信验证。浏览器携带缓存的“标识”询问服务器:“我上次存的这个版本的资源,现在还能用吗?” 服务器根据标识判断资源是否改变。

  • 触发条件:

    • 强缓存失效(Expires/max-age过期)。
    • 响应头设置了 Cache-Control: no-cache (跳过强缓存,直接协商)。
  • 控制字段:  涉及两组HTTP头:

    • 请求头 (由浏览器发送):

      • If-None-Match: 其值是上次响应中服务器返回的 ETag 值。例如 If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
      • If-Modified-Since: 其值是上次响应中服务器返回的 Last-Modified 值。例如 If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
    • 响应头 (由服务器返回):

      • ETag (Entity Tag):资源的唯一标识符(通常是内容的哈希值或版本号)。更精确,能感知内容字节级别的变化(即使修改时间没变)。例如 ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
      • Last-Modified:资源在服务器上最后修改的时间(GMT格式)。精度是秒级,如果文件修改非常快(毫秒内多次修改),可能无法准确判断。例如 Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
  • 协商缓存流程:

    1. 浏览器发现强缓存失效或需要协商。

    2. 浏览器准备请求:

      • 如果本地缓存有 ETag,则在请求头中添加 If-None-Match
      • 如果本地缓存有 Last-Modified,则在请求头中添加 If-Modified-Since。两者可能同时存在。
    3. 服务器收到请求:

      • 比较 If-None-Match 和服务器计算出的当前资源的 ETag
      • 或者比较 If-Modified-Since 和服务器上资源的最后修改时间。
      • 注意:ETag 的优先级通常高于 Last-Modified  服务器可以同时使用两者,但更倾向于使用 ETag 进行精确验证。
    4. 服务器响应:

      • 如果资源未修改 (ETag匹配 或 Last-Modified时间相同):

        • 返回 304 Not Modified 状态码。
        • 不返回资源内容(节省带宽)。
        • 可以更新响应头中的 Cache-Control/Expires/ETag/Last-Modified 等(可选,用于更新客户端缓存的有效期)。
      • 如果资源已修改 (ETag不匹配 或 Last-Modified时间不同):

        • 返回 200 OK 状态码。
        • 返回完整的、最新的资源内容
        • 在响应头中携带新的 Cache-Control/Expires/ETag/Last-Modified
    5. 浏览器收到响应:

      • 如果是 304直接使用本地缓存的资源
      • 如果是 200使用新的资源,并根据响应头更新本地缓存
  • 协商缓存生效时的表现:

    • Chrome DevTools Network Tab 中:状态码显示 304 Not Modified
    • 有网络请求发出,但没有资源内容传输(只有HTTP头),因此比强缓存慢,但比完整下载新资源快得多。

三、缓存位置 (Browser Cache Storage)

浏览器缓存通常存储在两个地方:

  1. Memory Cache (内存缓存):

    • 存储当前会话中频繁访问的小资源(如当前页面的CSS、JS、图片)。
    • 访问速度最快
    • 关闭浏览器标签页或进程后,缓存通常会被清除(取决于浏览器实现)。
    • DevTools Size列显示 (memory cache)
  2. Disk Cache (磁盘缓存):

    • 存储持久化的、较大的资源(如非当前页面的资源、大图片、字体文件)。
    • 容量更大,但访问速度比内存缓存慢。
    • 关闭浏览器后缓存通常保留(遵循 Cache-Control/Expires 的设置)。
    • DevTools Size列显示 (disk cache)

浏览器会根据资源大小、使用频率、缓存策略等自动决定资源存放在内存还是磁盘。开发者一般无法直接控制具体位置。

四、用户操作对缓存的影响

  • 正常浏览 (输入URL, 链接跳转):  浏览器会严格遵守缓存策略(强缓存 -> 协商缓存)。

  • 普通刷新/页面内刷新 (F5 / 按钮 / location.reload()):

    • 浏览器通常会跳过强缓存(视为 Cache-Control: max-age=0 或 no-cache)。
    • 进行协商缓存(发送 If-None-Match/If-Modified-Since)。
    • Cache-Control: immutable 的资源可能例外。
  • 强制刷新/硬刷新 (Ctrl+F5 / Cmd+Shift+R / location.reload(true)):

    • 浏览器忽略所有缓存(强缓存和协商缓存)。
    • 请求头中会添加 Cache-Control: no-cache 和 Pragma: no-cache,并且不会发送 If-None-Match/If-Modified-Since
    • 服务器必须返回 200 OK 和完整的新资源。
  • 清空缓存并硬刷新 (DevTools 功能):  清除本地缓存后再强制刷新。

五、最佳实践建议

  1. 区分资源类型设置缓存策略:

    • 带指纹/哈希的资源 (如 main.abcd1234.jsstyle.efgh5678.css):  内容改变,URL必然改变。设置超长强缓存 (如 Cache-Control: max-age=31536000, immutable)。用户获取新版本资源完全依赖URL改变。
    • 不带指纹但频繁变化的资源 (如 index.html, 入口文件):  设置 Cache-Control: no-cache 或很短的 max-age (如几秒),配合 ETag/Last-Modified 走协商缓存。确保用户能及时获取更新。
    • 几乎不变的静态资源 (如老版本库):  可以设置较长强缓存 (如几周或几个月)。
    • 敏感数据/API响应:  根据业务需求谨慎设置,通常 private 和较短的 max-age 或 no-store
  2. 优先使用 Cache-Control  比 Expires 更灵活、更可靠(不依赖客户端时间)。

  3. 优先使用 ETag  比 Last-Modified 更精确(能感知内容不变仅修改时间变,或快速连续修改)。

  4. 利用 immutable  对带哈希的资源非常有效,能显著改善刷新体验。

  5. 避免滥用 no-store  只在绝对必要时使用,否则会失去缓存的性能优势。

  6. CDN配置:  确保CDN正确理解并转发 Cache-ControlETag 等头部,并配置好自身的缓存规则。

总结流程图

text

浏览器请求资源
       |
       v
+---------------------+
| 检查强缓存是否有效?  | <---- (检查 Cache-Control / Expires)
| (本地缓存 + 未过期)  |
+---------------------+
         |
         | 有效
         v
+---------------------+         +---------------------+
|  使用本地缓存         |         |                     |
|  200 (from cache)   |         |                     |
+---------------------+         |                     |
         |                     |                     |
         | 无效                |                     |
         v                     |                     |
+---------------------+         |                     |
| 发送协商缓存请求      | --------> | 服务器验证           |
| (If-None-Match      |         | (ETag /            |
|  If-Modified-Since) |         |  Last-Modified)    |
+---------------------+         |                     |
         |                     |                     |
         |                     | 未修改               | 已修改
         |                     v                     v
         |             +---------------------+ +---------------------+
         |             | 返回 304 Not Modified| | 返回 200 OK + 新资源|
         |             +---------------------+ +---------------------+
         |                     |                     |
         |                     |                     |
         +<--------------------+                     |
         | 使用本地缓存                              | 使用新资源并更新缓存
         v                                           v

理解并合理应用强缓存和协商缓存,是优化Web应用加载速度和用户体验的关键步骤。务必根据资源的实际更新频率和特性选择合适的策略。