HTTP 的强缓存和协商缓存是怎么工作的?
简化版
浏览器缓存分两层:强缓存——在有效期内直接用本地缓存、根本不发请求(靠 Cache-Control、Expires);协商缓存——强缓存过期后,发个请求问服务器「我这份还能用吗」,没变就返回 304 让你继续用本地(靠 ETag/If-None-Match、Last-Modified/If-Modified-Since)。流程是:先查强缓存 → 过期再走协商缓存 → 都不行才真正下载。
详细版
强缓存(不发请求):
Cache-Control(HTTP/1.1,优先级高):max-age=3600表示 3600 秒内直接用缓存;还有no-cache(每次都协商)、no-store(完全不缓存)、public/private。Expires(HTTP/1.0,旧):一个绝对过期时间点。缺点是依赖客户端本地时间,时间不准就失效。Cache-Control优先级高于Expires。
在有效期内,浏览器直接从本地读取(磁盘/内存缓存),不发任何请求,控制台显示 200 (from disk/memory cache)。
协商缓存(发请求,可能返回 304):
强缓存过期后,浏览器带上标识去问服务器:
ETag/If-None-Match:ETag 是资源内容的「指纹」(哈希)。请求时带If-None-Match: <etag>,服务器比对内容指纹没变就回 304。Last-Modified/If-Modified-Since:资源的最后修改时间。请求时带If-Modified-Since: <时间>,服务器发现没改过就回 304。
命中协商缓存 → 服务器回 304 Not Modified(不带响应体),浏览器用本地缓存;没命中 → 回 200 带新内容。ETag 优先级高于 Last-Modified。
完整版教学
一、缓存的完整决策流程
浏览器请求一个资源时的缓存判断顺序:
① 有强缓存且未过期?
├─ 是 → 直接用本地缓存(不发请求,200 from cache)
└─ 否 ↓
② 走协商缓存:带 ETag/Last-Modified 发请求问服务器
├─ 服务器:没变 → 304 → 用本地缓存
└─ 服务器:变了 → 200 + 新内容 + 新的缓存标识
核心区别:强缓存不发请求(快,省一次往返),协商缓存发请求但可能不传数据(省下载大文件的带宽)。两者配合,既减少请求数又减少传输量。
二、强缓存:Expires 的坑与 Cache-Control 的改进
Expires 是 HTTP/1.0 的方案,给一个绝对时间点(如 Expires: Wed, 21 Oct 2025 07:28:00 GMT)。它的致命缺陷是依赖客户端本地时间——用户把系统时间改了,或客户端与服务器时间不同步,缓存判断就乱了。
Cache-Control: max-age 用的是相对时间(多少秒内有效),从收到响应开始算,不依赖绝对时间,更可靠。所以 HTTP/1.1 用它取代 Expires,且优先级更高(两者都存在时以 Cache-Control 为准)。
三、协商缓存:ETag 为什么比 Last-Modified 更准
Last-Modified 用「最后修改时间」判断,有几个问题:
- 精度只到秒:1 秒内多次修改感知不到;
- 时间变但内容没变:比如文件被重新生成(内容一样但时间戳变了),会误判为「已修改」而重新传输;
- 分布式部署下不同机器的文件修改时间可能不一致。
ETag(内容哈希指纹)直接看内容变没变,内容一样指纹就一样,精确得多。所以 ETag 优先级更高。代价是服务器要计算哈希,有一点开销。两者通常配合使用。
四、实战:不同资源用不同缓存策略
- 带哈希文件名的静态资源(如
app.a1b2c3.js):内容变文件名就变,可以设超长强缓存(Cache-Control: max-age=31536000, immutable)——反正内容变了 URL 就变,不会拿到旧的。 - HTML 入口文件:设
no-cache(每次都走协商缓存),保证用户总能拿到最新的页面,再由页面引用带哈希的静态资源。
这套「HTML 不强缓存 + 静态资源长强缓存 + 文件名带哈希」是前端部署的标准缓存方案,既能秒开又不会拿到旧版本。
五、常见误区
- ❌ 把
no-cache当成「不缓存」——no-cache是「每次都走协商缓存」,no-store才是完全不缓存。 - ❌ 以为强缓存也会发请求——强缓存命中不发请求,协商缓存才发(可能返回 304)。
- ❌ 以为 304 是错误——它是协商缓存命中,让浏览器用本地缓存,是正常且高效的。
- ❌ 只用 Last-Modified——它有精度和「时间变内容没变」的问题,ETag 更准、优先级更高。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 强缓存 | 未过期直接用本地缓存,不请求服务器 |
| 协商缓存 | 向服务器验证资源是否变化 |
| 优先级 | Cache-Control 通常优先于 Expires |
if fresh by Cache-Control:
use cache
else:
request with If-None-Match / If-Modified-Since
304 -> use cached body
强缓存决定“要不要发请求”,协商缓存决定“发了请求后能不能复用本地副本”。
- 误区:协商缓存命中就不会发请求。 协商缓存一定会向服务器验证,只是 304 时不返回完整 body。
- 误区:Expires 比 Cache-Control 更可靠。 Expires 依赖客户端时间,Cache-Control 用相对时间更可靠。
- 误区:ETag 和 Last-Modified 完全等价。 ETag 基于资源标识更精确,Last-Modified 受时间粒度和时钟影响。
- 追问:为什么静态资源常用 hash 文件名? 文件内容变化时 URL 变化,可设置长期强缓存而不担心更新失败。
- 追问:no-cache 是不缓存吗?
no-cache表示使用前必须协商验证,不等于不存;no-store才是不存。 - 追问:304 返回什么? 返回状态码和必要头部,不返回完整响应体,客户端复用本地缓存内容。
七、加强记忆
浏览器缓存两层:强缓存(Cache-Control: max-age/Expires,命中则不发请求直接用本地)→ 过期后协商缓存(ETag/Last-Modified,发请求问服务器,没变回 304 用本地)。Cache-Control 优先于 Expires(相对时间更可靠),ETag 优先于 Last-Modified(看内容比看时间准)。实战:HTML 走协商缓存、带哈希的静态资源走长强缓存。