主要分为两种:强缓存,协商缓存

  • 强缓存:浏览器请求资源时,通过Cache-ControlExpires响应头中的字段判断是否缓存命中,就从缓存中加载资源,不会向服务器发送请求
  • 协商缓存:当强缓存未命中时,通过请求头中的If-Modified-SinceIf-None-Match字段来判断资源是否发生变化
    • 如果资源未变化,服务器返回304 Not Modified状态码,浏览器继续使用缓存资源。
    • 如果资源已变化,服务器返回200 OK状态码和新资源。

实际工程中怎么用

核心思路

能用强缓存就用强缓存(零网络开销),但要解决”怎么更新”的问题。不能保证内容稳定的,用协商缓存兜底。

静态资源(JS / CSS / 图片)→ 强缓存 + 文件名哈希

这是现代前端项目最标准的做法。Webpack / Vite 打包时,会把文件内容的哈希值注入文件名:

main.a3f9c2d1.js
style.7b2e4f8a.css

然后服务器对这类资源设置超长强缓存:

Cache-Control: public, max-age=31536000, immutable
  • immutable 告诉浏览器”这个文件永远不会变,连协商请求都不用发”
  • 文件变了?打包出来的文件名哈希就变了,浏览器当成全新 URL,自动拉最新

结果: 老用户二次访问,JS/CSS 全部命中强缓存,0 网络请求,页面秒开。


HTML 入口文件 → 协商缓存(no-cache

HTML 是整个应用的入口,它引用了带哈希的 JS/CSS 文件名。HTML 本身必须每次都能拿到最新,否则用户永远加载旧版本的 JS。

Cache-Control: no-cache

浏览器每次都会带着 If-None-Match 去问服务器:“HTML 变了吗?”

  • 没变 → 返回 304,用本地缓存,几乎无开销
  • 变了(新版上线)→ 返回新 HTML,新 HTML 里的 JS/CSS 文件名哈希也变了,浏览器自动拉新资源

千万不要给 HTML 设强缓存

一旦 HTML 被强缓存缓住,新版本上线后用户完全感知不到,只能等缓存自然过期,无法热修复。


CDN 上的资源 → 区分 publicprivate

# 静态资源,CDN 可以缓存,所有人共用
Cache-Control: public, max-age=31536000
 
# 用户个人数据(头像、账单),只能浏览器缓存,CDN 不能缓
Cache-Control: private, no-cache

public 的意义在于:同一个资源,全球用户第一次请求后,CDN 就缓存了,后续用户直接从最近的 CDN 节点拿,源站压力大幅降低。


API 接口 → 视场景而定

接口类型推荐策略原因
实时数据(股票、消息)no-store不允许任何缓存,每次都要最新数据
低频变动的配置类数据(城市列表、分类)public, max-age=3600数据几乎不变,强缓存 1 小时完全可以
用户个人信息private, no-cache私有数据,走协商缓存保证拿到最新
POST / PUT / DELETE 请求不缓存(浏览器默认不缓存)写操作天然不缓存

强制更新缓存的几种手段

上线新版但用户还在用旧缓存,怎么办?

  1. 文件名哈希(最优):让 Vite/Webpack 帮你自动处理,无需手动干预
  2. Query String(简单粗暴):main.js?v=20240326,改 v 参数让浏览器认为是新 URL
  3. Service Worker:可以完全接管缓存逻辑,实现精细化控制(适合 PWA)
  4. 强制刷新:用户 Ctrl+Shift+R 会跳过强缓存,临时应急手段

决策流程

请求一个资源
    │
    ├─ 是 HTML 入口?
    │       └─ Cache-Control: no-cache(协商缓存)
    │
    ├─ 是带哈希的静态资源(JS/CSS/图片)?
    │       └─ Cache-Control: public, max-age=31536000, immutable(强缓存)
    │
    ├─ 是 API?
    │       ├─ 实时数据 → no-store
    │       ├─ 配置类数据 → public, max-age=合适的秒数
    │       └─ 用户私有数据 → private, no-cache
    │
    └─ 包含用户敏感信息?
            └─ 一定要用 private,禁止 CDN 缓存

Cache-Control (HTTP/1.1 )是目前最常用的字段,通过设置相对时间来控制。

  • max-age=31536000: 表示资源在 31536000 秒(1年)内是新鲜的,直接用缓存。
  • no-cache: 不要被名字骗了!不是“不缓存”,而是“缓存,但在使用前必须去服务器验证一下(走协商缓存)”。
  • no-store: 真正的“不缓存”,任何时候都去服务器下载最新的。
  • public / private: public 表示 CDN 也可以缓存;private 表示只有浏览器能缓存(比如存了用户敏感信息的页面)。

当不使用强缓存的时候,就要用协商缓存,有两种方式实现协商

  • ETag/If - Noe -Match :在访问文件的时候,比较上次访问文件给的ETag,即由第一次文件内容生成的哈希值,与第二次访问文件的If - None - Match进行对比,如果相同说明文件没变,返回状态码304,如果变了,则重新访问,返回200与新数据
  • Last- Modified / If - Modified - Since:这是一个很粗糙的比较,是将上一次与这一次文件的文件最后修改时间,如果修改时间一样,说明没有修改,返回304,变了,则重新访问文件

区分强缓存协商缓存步骤

  1. Step 1: 检查 Cache-ControlExpires
    • 如果未过期 强缓存命中,直接读取资源。
  2. Step 2: 如果过期了,带上 ETagLast-Modified 发起请求。
    • 服务器比对凭据。
  3. Step 3:
    • 凭据一致 返回 304,浏览器从缓存读。
    • 凭据不一致 返回 200,服务器传回新资源并更新缓存。

reference

前端知识速记:浏览器缓存机制 - 强缓存与协商缓存