🍪🧁🍧

浏览器缓存

浏览器缓存策略详解

一、为什么需要浏览器缓存?

  • 提升性能:用户再次访问时,可以直接从本地加载资源,速度远快于从服务器下载,缩短页面加载时间。
  • 减少服务器负载:减少了对服务器的请求次数,降低服务器压力。
  • 节省带宽:减少了不必要的数据传输。

二、缓存位置

浏览器缓存的资源可能存储在几个不同的位置,优先级从高到低通常是:

  1. Service Worker Cache:通过 Service Worker 中的 Cache API 进行程序化控制,非常灵活和强大,是 PWA (Progressive Web Apps) 的核心。
  2. Memory Cache (内存缓存):速度最快,但生命周期短,通常在浏览器标签页关闭后就清除了。用于存储当前页面正在使用或刚下载的资源。
  3. Disk Cache (磁盘缓存):存储在硬盘上,生命周期较长,可以在多次会话之间甚至浏览器重启后依然存在(只要没过期或被清除)。
  4. Push Cache (推送缓存):HTTP/2 的服务器推送功能可以将资源推送到这里,但使用和支持情况相对复杂。

我们通常讨论的 HTTP 缓存策略主要影响的是 Memory Cache 和 Disk Cache。

三、HTTP 缓存策略

HTTP 缓存策略通过 HTTP 响应头和请求头来控制。

1. 强缓存 (Strong Caching) - 不向服务器发送请求

强缓存机制下,如果缓存资源未过期,浏览器会直接从本地缓存中读取资源,不会向服务器发送任何 HTTP 请求

控制强缓存的 HTTP 响应头字段主要有两个:

  • Expires (HTTP/1.0):

    • 指定一个绝对的过期日期和时间 (GMT 格式)。例如:Expires: Wed, 21 Oct 2026 07:28:00 GMT
    • 缺点:依赖于客户端和服务器端的时间是否同步,如果客户端时间不准确,可能导致缓存提前或延迟失效。
  • Cache-Control (HTTP/1.1 - 优先使用):

    • 提供了更灵活和强大的缓存控制指令,优先级高于 Expires
    • 常用指令:
      • max-age=<seconds>: 指定资源在被视为“新鲜”的最长时间(以秒为单位),这是一个相对时间,从响应生成时开始计算。例如:Cache-Control: max-age=3600 (缓存1小时)。
      • s-maxage=<seconds>: 类似于 max-age,但仅适用于共享缓存(如 CDN、代理服务器)。其优先级高于 max-age
      • public: 表明响应可以被任何缓存(浏览器、CDN、代理)缓存。
      • private: 表明响应是针对单个用户的,只能被用户的浏览器缓存,不能被共享缓存缓存。
      • no-store: 禁止浏览器和所有中间缓存存储任何版本的返回响应。每次请求都必须向服务器重新获取完整资源。
      • no-cache: 资源可以被缓存,但是浏览器在每次使用缓存副本之前,必须向服务器发送请求进行验证 (协商缓存),确认资源是否仍然有效。容易误解为“不缓存”,实际是“使用前验证”。

2. 协商缓存 (Conditional Caching / Validation) - 向服务器发送请求确认

当强缓存过期(例如 max-age 时间已到),或者资源被标记为 Cache-Control: no-cache 时,浏览器会启用协商缓存。

浏览器会向服务器发送一个 HTTP 请求,但这个请求会带上一些特殊的头部字段,让服务器判断浏览器本地缓存的资源是否仍然有效。

  • 如果服务器判断资源未改变,会返回一个 304 Not Modified 状态码,并且响应体为空。浏览器收到 304 后,就直接使用本地缓存的副本。这样只传输了很小的头部信息,节省了带宽。
  • 如果服务器判断资源已改变,会返回一个 200 OK 状态码,并在响应体中包含新的资源内容。浏览器会使用新资源并更新本地缓存。

控制协商缓存的 HTTP 头部字段主要有两对(服务器响应头 -> 客户端下次请求头):

  • Last-Modified / If-Modified-Since:

    1. 服务器在响应中通过 Last-Modified: <date> 告知浏览器资源的最后修改时间。
    2. 浏览器缓存该资源和这个时间。
    3. 下次请求该资源时,浏览器在请求头中带上 If-Modified-Since: <last-modified-date>
    4. 服务器比较这个时间和资源的实际最后修改时间。若一致,返回 304;否则返回 200 和新资源。
    • 缺点:时间戳只能精确到秒;如果文件内容没变但修改时间变了(或反之),可能导致不准确。
  • ETag / If-None-Match (更优):

    1. 服务器在响应中通过 ETag: "<entity-tag>" (Entity Tag) 提供一个资源特定版本的唯一标识符(通常是内容的哈希值或版本号)。
    2. 浏览器缓存该资源和这个 ETag。
    3. 下次请求该资源时,浏览器在请求头中带上 If-None-Match: "<entity-tag>"
    4. 服务器比较浏览器提供的 ETag 和当前资源的 ETag。若一致,返回 304;否则返回 200 和新资源(以及新的 ETag)。
    • 优点:比 Last-Modified 更精确,能识别内容是否真的发生了变化。ETag 的优先级通常高于 Last-Modified。如果两者都存在,服务器会优先使用 ETag。

有Etag了 为什么还要用Last-Modified

  • 对于Web服务器来说,获取文件的最后修改时间(Last-Modified)是非常直接和低成本的操作,因为文件系统本身就记录了这个信息。相比之下,生成一个基于内容的 ETag(例如通过计算文件的哈希值)可能需要读取整个文件内容,对于大文件或者I/O敏感的系统,这可能会带来额外的开销。
  • Last-Modified 提供了一个人类可读的日期时间戳。这在调试缓存问题或简单查看资源何时更新时非常方便,而 ETag 通常是一个不透明的字符串。
  • 对于某些动态生成的内容或流式内容,可能很难或不方便为其计算一个稳定且唯一的 ETag。

四、缓存决策流程

浏览器加载资源时的大致缓存决策流程:

  1. 检查 Cache-Control 是否为 no-store,如果是,则直接请求服务器,不使用任何缓存。
  2. 否则,检查本地缓存:
    a. 查找是否有匹配的缓存资源。
    b. 强缓存判断
    i. 检查 Cache-Controlmax-ageExpires 是否仍然有效(未过期)。
    ii. 如果有效(新鲜),则直接使用缓存(命中强缓存),状态码通常是 200 OK (from memory cache / from disk cache)不发送 HTTP 请求。
    c. 协商缓存判断(如果强缓存失效或 Cache-Control: no-cache):
    i. 向服务器发送 HTTP 请求,带上 If-Modified-Since (基于上次的 Last-Modified) 和/或 If-None-Match (基于上次的 ETag)。
    ii. 服务器根据这些头部判断资源是否有更新:
    - 未更新:返回 304 Not Modified。浏览器使用本地缓存。
    - 已更新:返回 200 OK 和新资源。浏览器使用新资源并更新缓存。
  3. 如果本地没有缓存,则直接向服务器请求完整资源,服务器返回 200 OK 和资源内容,浏览器根据响应头决定如何缓存。

五、用户行为对缓存的影响

  • 地址栏回车 / 链接跳转 / 前进后退按钮:正常使用缓存策略。
  • F5 / Ctrl+R (Mac: Cmd+R) - 普通刷新:通常会跳过强缓存,直接发起协商缓存请求(即带上 If-Modified-Since / If-None-Match)。
  • Ctrl+F5 / Ctrl+Shift+R (Mac: Cmd+Shift+R) - 强制刷新:完全忽略所有缓存(强缓存和协商缓存),直接向服务器请求全新资源。请求头中可能会带上 Cache-Control: no-cachePragma: no-cache

六、缓存策略建议

  • 不常变动的静态资源 (JS, CSS, 图片等):使用长 max-age (如一年) + 文件名带哈希值 (例如 bundle.[contenthash].js) 进行版本控制。当文件内容改变,哈希值改变,文件名改变,浏览器会请求新文件。
  • 经常变动的资源 (如 HTML 文件本身):使用 Cache-Control: no-cache (使其总是进行协商缓存) 或较短的 max-age 并配合 ETagLast-Modified
  • 绝对不能缓存的敏感数据:使用 Cache-Control: no-store

理解并合理配置缓存策略,对于提升网站性能和用户体验至关重要。

Web Storage 使用场景总结

一、localStorage (本地持久化存储)

核心特性:数据永久保存(除非手动清除),同源窗口/标签页共享。

主要使用场景

  1. 用户偏好设置
    • 网站主题(如暗黑/明亮模式)
    • 字体大小、音量等个性化配置
    • “不再提示”类消息的状态
  2. 持久化的应用状态
    • 未登录用户的购物车(若希望关闭浏览器后仍保留)
    • 应用教程引导完成状态
    • “记住我”的非敏感标识
  3. 轻量级离线数据
    • 简单PWA或Web应用的离线内容(如笔记、待办事项)
    • 缓存不常变的API数据或应用配置
  4. 性能优化
    • 缓存计算成本高但不常变的数据,减少重复计算/请求

二、sessionStorage (会话级临时存储)

核心特性:数据仅在当前浏览器标签页会话期间有效,关闭标签页即清除,标签页之间数据隔离。

主要使用场景

  1. 临时性用户输入
    • 多步骤表单的临时数据保存(防意外刷新/关闭)
    • 当前会话的搜索历史(仅限当前标签)
  2. 当前会话的应用状态
    • 单页应用(SPA)中当前页面的临时状态(如组件开合、筛选条件)
    • 一次性操作成功后的提示信息(刷新或关闭后消失)
  3. 特定于标签页的数据
    • Web应用在不同标签页打开独立工作区时的状态隔离
  4. 防止重复提交
    • 存储一次性令牌,用于表单提交验证

共同注意事项

  • 安全:切勿存储敏感信息(如密码、完整Token),易受XSS攻击。
  • 类型:只能存字符串,对象/数组需 JSON.stringify()JSON.parse()
  • 大小:通常5-10MB,不适合大量数据。
  • API:同步操作,注意对性能的潜在影响。
posted @ 2025-05-14 23:34  不想吃fun  阅读(33)  评论(0)    收藏  举报