2749 words
14 minutes
浏览器缓存机制:强缓存、协商缓存与缓存更新策略

浏览器缓存是前端面试里最经典的 HTTP 题之一。很多人会背“强缓存和协商缓存”,但如果面试官继续追问 Cache-ControlETagVary、CDN 缓存、资源更新策略,回答往往就会断掉。

这篇文章的目标不是背答案,而是把浏览器缓存真正讲透。


一、先说结论:浏览器缓存到底解决什么问题?#

浏览器缓存的本质目标有三个:

  1. 减少重复请求
  2. 降低网络延迟
  3. 减轻服务器压力

如果没有缓存,一个用户每次刷新页面都要重新拉取:

  • HTML
  • CSS
  • JavaScript
  • 图片
  • 字体
  • 接口返回结果

这不仅浪费流量,也会让页面变慢。

所以缓存本质上就是一句话:

如果资源没变,就尽量不要重新下载。


二、浏览器缓存并不是只有一种#

HTTP 缓存里,常见要分清两个概念:

2.1 私有缓存(Private Cache)#

也就是浏览器本地缓存

特点:

  • 只属于当前用户
  • 可以缓存个性化内容
  • 常见于静态资源、页面、接口结果

2.2 共享缓存(Shared Cache)#

也就是位于客户端和源站之间的缓存,例如:

  • CDN
  • 反向代理
  • 网关缓存

特点:

  • 多个用户共享
  • 更适合缓存公共资源
  • 需要特别注意个性化内容泄漏风险

MDN 对缓存类型的示意图如下:

HTTP cache types

这也是为什么缓存问题不能只谈“浏览器有没有命中”,还要考虑:

  • CDN 是否命中
  • 浏览器是否本地命中
  • 是否需要回源校验

三、浏览器缓存整体流程#

最经典的缓存流程可以简化成这样:

Caching example

第一次请求:

  1. 浏览器请求资源
  2. 服务器返回资源和缓存相关响应头
  3. 浏览器保存资源和元信息

后续再次请求时:

  1. 浏览器先看本地缓存是否还能直接用
  2. 如果还能直接用,就不发请求
  3. 如果不能直接用,就带上条件请求头去问服务器
  4. 服务器告诉浏览器“没变”还是“变了”

于是就形成了两个核心机制:

  • 强缓存
  • 协商缓存

四、强缓存:本地还新鲜,就直接用#

强缓存的特点是:

命中后,浏览器直接使用本地副本,不发送请求到服务器。

这也是最省的一种缓存方式。

4.1 强缓存依赖哪些响应头?#

主要是两个:

  • Cache-Control
  • Expires

其中现代开发里更推荐 Cache-Control

4.2 Expires#

这是 HTTP/1.0 的老方案,用一个明确时间表示过期时间:

Expires: Tue, 28 May 2026 12:00:00 GMT

问题在于它依赖客户端和服务端时间同步,系统时钟偏差会带来问题。

4.3 Cache-Control#

这是现代缓存控制的核心。

最常见写法:

Cache-Control: max-age=3600

表示这个响应在 3600 秒内是新鲜的,可以直接使用。

也就是说,只要资源年龄还没超过 max-age,浏览器就不会重新请求服务器。


五、Cache-Control 常见指令详解#

5.1 max-age#

Cache-Control: max-age=604800

表示资源在 604800 秒(7 天)内都是 fresh 的。

5.2 public#

Cache-Control: public, max-age=604800

表示响应可以被共享缓存(如 CDN)缓存。

5.3 private#

Cache-Control: private

表示只能被浏览器私有缓存缓存,不能被共享缓存缓存

常用于:

  • 用户资料页
  • 登录后的个性化响应
  • 带 cookie 且内容因用户而异的页面

5.4 no-cache#

这是一个非常容易被误解的指令。

Cache-Control: no-cache

它的意思不是“不缓存”,而是:

可以缓存,但每次使用前都必须向服务器重新验证。

也就是说,它更接近“必须协商后才能用”,而不是“完全不能存”。

5.5 no-store#

Cache-Control: no-store

这个才是真正的:

不要存。

适合:

  • 银行交易页
  • 极高敏感信息页面
  • 不希望落地任何缓存副本的响应

5.6 immutable#

Cache-Control: public, max-age=31536000, immutable

它的意思是:

在 fresh 期间,这个资源肯定不会变。

特别适合带 hash 的静态资源:

  • app.8a72f3.js
  • main.2ef31c.css

这种资源一旦内容变化,文件名也会变化,因此可以安全地配一个很长的缓存时间。

5.7 stale-while-revalidate#

Cache-Control: max-age=600, stale-while-revalidate=60

表示:

  • 前 600 秒内正常 fresh
  • 过期后 60 秒内,可以先返回旧内容,同时后台重新验证

这是一种兼顾速度与新鲜度的策略。


六、强缓存什么时候会失效?#

强缓存的判断核心是:

资源是否仍然 fresh。

一旦过期,浏览器就不能直接用本地副本了,下一步会进入:

  • 协商缓存
  • 或直接重新下载

七、协商缓存:资源过期了,但不一定要重下#

协商缓存的特点是:

浏览器会向服务器发请求,但会带上“条件”,让服务器判断资源是否真的变了。

如果没变,服务器只返回:

304 Not Modified

浏览器继续使用本地缓存副本。

所以协商缓存的价值是:

  • 虽然还要发请求
  • 但不用重新传完整资源内容

八、协商缓存依赖哪些响应头和请求头?#

有两套常见方案:

8.1 Last-Modified / If-Modified-Since#

服务器第一次响应:

Last-Modified: Wed, 29 May 2026 10:00:00 GMT

下次请求时,浏览器会带上:

If-Modified-Since: Wed, 29 May 2026 10:00:00 GMT

服务器检查资源最后更新时间:

  • 如果没变,返回 304
  • 如果变了,返回新的 200 和新内容

8.2 ETag / If-None-Match#

服务器第一次响应:

ETag: "f4a1b9"

下次请求时浏览器带上:

If-None-Match: "f4a1b9"

服务器比较当前资源标识:

  • 一样:返回 304
  • 不一样:返回 200 和新资源

九、ETagLast-Modified 有什么区别?#

Last-Modified 的优点#

  • 简单
  • 成本低
  • 很多静态资源天然适合

Last-Modified 的缺点#

  • 精度通常到秒
  • 秒级内多次修改可能检测不准
  • 内容没变但文件时间变了,也会误判为“变了”

ETag 的优点#

  • 更精确
  • 更适合判断“内容是否真的变化”

ETag 的缺点#

  • 服务端需要生成标识
  • 某些分布式场景下处理不当会带来额外复杂度

实战建议#

常见优先级一般是:

ETag > Last-Modified

如果两者都有,通常优先使用 ETag 做判断。


十、强缓存和协商缓存的关系#

很多人会把它们当成二选一,其实不是。

常见流程是:

  1. 先看强缓存
  2. 如果强缓存命中,直接用本地资源
  3. 如果强缓存没命中,再走协商缓存
  4. 如果协商成功,返回 304
  5. 如果协商失败,返回新的 200

所以更准确地说:

  • 强缓存是第一层
  • 协商缓存是第二层

十一、浏览器缓存、CDN 缓存、Service Worker 缓存有什么区别?#

11.1 浏览器缓存#

  • 用户本地
  • 属于私有缓存
  • 主要由 HTTP 头控制

11.2 CDN 缓存#

  • 位于边缘节点
  • 属于共享缓存
  • 常结合 Cache-Control、CDN 控制台规则使用

11.3 Service Worker 缓存#

  • 由前端代码主动控制
  • 可以实现离线缓存、预缓存、运行时缓存
  • 适合做更灵活的缓存策略

换句话说,HTTP 缓存是浏览器和中间层默认行为,而 Service Worker 缓存是前端主动编排的缓存逻辑。


十二、Vary 为什么重要?#

如果同一个 URL 在不同条件下返回不同内容,就不能只按 URL 缓存。

例如同一个地址会根据:

  • 语言
  • 编码
  • 设备能力

返回不同内容。

这时就需要:

Vary: Accept-Language

这表示缓存键不能只看 URL,还要把 Accept-Language 一起算进去。

否则就可能出现:

  • 中文用户拿到英文缓存
  • 移动端拿到桌面端内容

十三、真实项目中的缓存更新策略#

面试里如果只会说原理,往往还不够。更高分的回答应该讲:

线上资源怎么安全更新?

13.1 对 HTML:短缓存或不强缓存#

HTML 通常变化频繁,且它引用整个资源入口,因此一般不建议超长强缓存。

常见策略:

Cache-Control: no-cache

这样浏览器每次都会向服务器确认 HTML 是否更新。

13.2 对 JS / CSS / 图片:长缓存 + 文件名 hash#

例如:

  • app.89d12.js
  • vendor.a83f1.css

策略通常是:

Cache-Control: public, max-age=31536000, immutable

这样做的逻辑是:

  • 只要文件名不变,就说明内容不变
  • 内容变了,构建产物 hash 也变,URL 自动变化

这就是最经典的 cache busting

13.3 对接口:按业务类型区分#

例如:

  • 用户资料:private, no-cache
  • 新闻列表:短 max-age 或 SWR
  • 配置接口:可协商缓存
  • 金融交易:no-store

十四、前端面试里最常见的几个误区#

14.1 no-cache 不是不缓存#

它是“先存下来,但每次用前都校验”。

14.2 no-store 才是真不缓存#

这个一定要分清。

14.3 Expires 不是首选#

现代开发优先用 Cache-Control: max-age

14.4 强缓存和协商缓存不是对立关系#

它们通常是串联关系,不是互斥关系。

14.5 只谈浏览器缓存不够#

真实线上环境里,经常还要结合:

  • CDN
  • Nginx
  • 反向代理
  • Service Worker

一起看缓存策略。


十五、面试里怎么回答“浏览器缓存机制”?#

如果是面试题,我建议按下面结构回答:

第一步:先讲目标#

浏览器缓存的目的是减少重复请求、提升访问速度、降低服务器压力。

第二步:讲两层机制#

浏览器缓存分为:

  • 强缓存
  • 协商缓存

强缓存命中时浏览器直接使用本地副本,不发请求;协商缓存则会发条件请求,由服务器判断资源是否变化。

第三步:讲关键头部#

强缓存:

  • Cache-Control
  • Expires

协商缓存:

  • ETag / If-None-Match
  • Last-Modified / If-Modified-Since

第四步:讲实际更新策略#

真实项目里通常会把:

  • HTML 配成 no-cache
  • 带 hash 的静态资源配成长缓存 max-age + immutable

第五步:补一个进阶点#

如果内容会因为语言、编码等条件变化,还要配合 Vary;线上还要结合 CDN 缓存一起设计。

这样回答,基本就已经很完整了。


十六、总结#

浏览器缓存机制可以浓缩成一句话:

优先直接复用本地副本,实在不能直接复用时,再低成本地向服务器确认资源是否变化。

展开以后就是完整体系:

  1. 强缓存控制“能不能直接用”
  2. 协商缓存控制“过期后能不能继续用旧副本”
  3. Cache-Control 是现代缓存核心
  4. ETagLast-Modified 负责条件验证
  5. Vary 负责多版本缓存隔离
  6. 实战中要结合 HTML、静态资源、接口、CDN 一起设计

真正理解了这套机制,浏览器缓存这道题就不再是“背概念”,而是一个完整的工程问题。

浏览器缓存机制:强缓存、协商缓存与缓存更新策略
https://fuwari.vercel.app/posts/browser-cache-mechanism/
Author
Owen
Published at
2026-05-29
License
CC BY-NC-SA 4.0