一、 缓存基本原理
定义:首次请求后,浏览器(或代理服务器)会保存一份请求资源的响应副本。当用户再次发起相同请求时,若判断缓存命中,则拦截请求,直接将存储的副本返回给用户,从而避免重新向服务器发起网络请求。
💡缓存的优点
- 减少冗余数据传输:节省网络带宽和流量费用。
- 减轻服务器负担:大幅降低服务器并发压力,提升网站整体性能。
- 提升用户体验:加快客户端网页和资源的加载速度,实现"秒开"。
二、 缓存分类与核心区别
HTTP 缓存主要分为两类:强制缓存 和 协商缓存。
| 维度 | 强制缓存 (Strong Cache) | 协商缓存 (Negotiation Cache) |
|---|---|---|
| 核心机制 | 浏览器不向服务器发送请求,直接从本地缓存读取资源。 | 浏览器向服务器发送请求,询问资源是否更新,由服务器决定是否使用缓存。 |
| 状态码表现 | 200 OK (from memory/disk cache) |
命中返回 304 Not Modified,未命中返回 200 OK |
| 适用场景 | 资源不常变动,或文件名带有 Hash 值的静态资源。 | 资源频繁变动,且必须保证数据实时性的场景(如 HTML 文件)。 |
核心痛点引出 :强制缓存直接读取本地数据,如果在此期间服务器上的资源被修改了,用户看到的依然是旧数据。为了解决资源更新 的问题,引入了协商缓存。
三、 强制缓存 (Strong Cache)
强制缓存主要依靠响应头(Response Headers)中的参数来控制。
1. Cache-Control (HTTP/1.1,相对时间,优先级高)
当值设置为 max-age=xxx(单位为秒)时,代表在请求正确返回后的 xxx 秒内再次加载资源,会直接命中强制缓存。

常用指令解析:
**max-age=xxx**:缓存有效时间,单位为秒。**no-cache**:并非不缓存 ,而是不直接使用本地缓存。强制要求使用协商缓存,向服务器确认资源是否更改(若响应中有 ETag,则带上 ETag 去校验)。**no-store**:真正的禁止缓存。浏览器不保存任何数据,每次请求都会向服务器下载完整资源。**public**:资源可被所有用户缓存(包括终端用户浏览器和 CDN 等中间代理服务器)。**private**:资源只能被终端用户的浏览器缓存,不允许 CDN 等中间代理服务器缓存(默认值)。**must-revalidate**:缓存必须在使用之前验证其状态,不允许使用过期缓存。
2. Expires (HTTP/1.0,绝对时间,优先级低)
- 机制 :服务器返回的一个绝对过期时间(如
Wed, 22 Nov 2023 08:41:00 GMT)。 - 缺陷:受限于本地时间,如果客户端本地时间被修改,会导致缓存判断失效。
- 优先级 :当
Cache-Control和Expires同时存在时,**Cache-Control**优先级高于**Expires**。
3. 强制缓存的读取位置
在 Chrome Network 面板中,强缓存命中会显示 200,但来源分为两种:
**from memory cache**:从内存中读取。速度极快,但关闭浏览器标签页后缓存即失效。**from disk cache**:从硬盘中读取。速度稍慢,但持久化存储,关闭浏览器后依然存在。
四、 协商缓存 (Negotiation Cache)
当强制缓存失效(或未设置强制缓存、设置了 no-cache)时,浏览器会携带缓存标识向服务器发起请求,由服务器决定是否返回 304。
1. 基于最后修改时间:Last-Modified / If-Modified-Since
-
首次请求 :服务器响应头带上
Last-Modified,记录资源在服务器上的最后修改时间 (精度到秒)。 -
再次请求 :浏览器请求头带上
If-Modified-Since,值为之前缓存的Last-Modified。 -
服务器校验 :对比资源的最新修改时间与
If-Modified-Since。 -
- 相同 :资源未修改,返回
**304**,浏览器读取本地缓存。 - 不同 :资源已修改,返回
**200**及新资源。
- 相同 :资源未修改,返回


2. 基于文件内容 Hash:ETag / If-None-Match
-
首次请求 :服务器响应头带上
ETag,值为资源文件的唯一标识/Hash值(生成规则由服务器决定)。 -
再次请求 :浏览器请求头带上
If-None-Match,值为之前缓存的ETag。 -
服务器校验 :对比当前资源的 ETag 与
If-None-Match。 -
- 相同 :命中协商缓存,返回
**304**。 - 不同 :未命中,返回
**200**及新资源。
- 相同 :命中协商缓存,返回


3. 🆚 ETag 与 Last-Modified 深度对比
| 对比维度 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级。如果文件在 1 秒内被修改多次,无法识别。 | 内容级。只要文件内容改变,Hash 值必变,精度极高。 |
| 性能 | 高。只需读取文件的最后修改时间,开销极小。 | 低。需要计算文件内容的 Hash 值,会加大服务器 CPU 开销。 |
| 优先级 | 较低。 | 较高 。服务器校验时优先以 ETag 为准。 |
| 适用场景 | 文件较大且修改频率不高的普通资源。 | 文件较小、修改频繁,或需要极高精度校验的资源。 |
💡 毫秒级修改问题 :如果一个资源在 1 秒内被修改了多次,Last-Modified 无法感知变化,必须配合 ETag 使用才能确保拿到最新数据。
ETag/If-None-Match 的出现主要解决了 Last-Modified/If-Modified-Since 所解决不了的问题:
- 如果文件的修改频率在秒级以下,Last-Modified/If-Modified-Since 会错误地返回 304
- 如果文件被修改了,但是内容没有任何变化的时候,Last-Modified/If-Modified-Since 会错误地返回 200,上面的例子就说明了这个问题
五、 缓存策略与最佳实践 (重点补充)
在实际的前端工程化开发中,通常会组合使用强制缓存和协商缓存,以达到性能与数据一致性的最佳平衡:
1. HTML 文件:使用【协商缓存】
- 策略 :设置
Cache-Control: no-cache。 - 原因:HTML 是页面的入口,如果 HTML 被强缓存,即使 JS/CSS 更新了,用户也无法加载到新的 HTML,导致页面无法更新。使用协商缓存可以保证每次都能拿到最新的 HTML 结构。
2. 静态资源 (JS/CSS/图片/字体):使用【长期强制缓存】
- 策略 :设置
Cache-Control: max-age=31536000(一年),并在打包时给文件名加上 ContentHash (如app.a1b2c3d4.js)。 - 原因:只要文件内容不变,文件名就不变,直接命中强缓存,实现"秒开";一旦文件内容修改,Hash 值改变,文件名改变,浏览器会认为是新资源从而重新请求,完美避开缓存不更新的问题。
3. 频繁变动的 API 数据:【不缓存】或【短期缓存】
- 策略 :设置
Cache-Control: no-store或极短的max-age。 - 原因:保证用户每次都能看到最新的业务数据。
六、 补充注意事项 (避坑指南)
- 请求方法限制 :浏览器只对
**GET**请求进行缓存 ,POST、PUT、DELETE等请求默认不会被缓存。 - URL 参数影响 :缓存是基于完整 URL 的。如果 URL 带了不同的查询参数(如
?v=1和?v=2),浏览器会认为是两个不同的资源,从而导致缓存失效。 - 手动清除缓存 :在代码中无法直接清除用户的浏览器缓存,只能通过更改文件名(加 Hash)或更改 URL 参数(加时间戳/版本号) 来"绕过"旧缓存,这被称为"缓存破除(Cache Busting)"。
- HTTPS 与缓存:HTTPS 下的缓存机制与 HTTP 基本一致,但某些中间代理(如共享代理)出于安全考虑,可能不会缓存 HTTPS 响应,此时主要依赖浏览器本地缓存。
七、常见面试题解析
1. 强缓存和协商缓存有什么区别?
- 核心区别:是否发送 HTTP 请求到服务器
- 强缓存 :不发请求,直接用本地缓存,状态码
200 (from cache) - 协商缓存:发请求验证,服务器返回 304 则用缓存,返回 200 则更新缓存
2. no-cache 和 no-store 有什么区别?
**no-cache**:不是不缓存,而是每次使用前必须去服务器验证有效性(协商缓存)**no-store**:完全禁止缓存,每次请求都从服务器获取完整资源,且响应内容也不被缓存
3. F5 刷新和 Ctrl+F5 强制刷新有什么不同?
- F5 刷新 :浏览器会跳过强缓存阶段,但会带上协商缓存头(
If-Modified-Since/If-None-Match)去服务器验证资源有效性 - Ctrl+F5 强制刷新 :彻底绕过所有缓存,请求头带
Cache-Control: no-cache和Pragma: no-cache,强制从服务器获取最新资源