一、强缓存(强制缓存)
1.1 定义与现象
当响应在浏览器侧仍被视为有效时,浏览器可以不发起网络请求(或仅做非常有限的本地判断),直接使用本地副本,开发者工具里常见:
- 200 (from memory cache):从内存读取,多见于脚本、图片、字体等;关闭标签页或浏览器后通常释放。
- 200 (from disk cache):从磁盘读取,多见于 CSS 等;关闭浏览器后仍可能保留(与实现与策略有关)。
1.2 控制强制缓存的响应头
强缓存主要由响应头中的过期信息控制,常见三类:
| 响应头 | 协议/背景 | 作用简述 |
|---|---|---|
| Expires | HTTP/1.0 | 绝对过期时间(GMT)。依赖客户端与服务器时钟一致,时钟偏差会导致提前/延后过期。 |
| Cache-Control | HTTP/1.1 | 现代首选,指令丰富,可表达 max-age、no-store、no-cache、public/private 等。 |
| Pragma | HTTP/1.0 遗留 | 常见为 Pragma: no-cache,多用于兼容;在响应中的语义与「禁用缓存/需验证」类需求相关(见下文误区)。 |
Expires:
值为服务器返回的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。(HTTP1.0的属性,缺点是客户端和服务器时间不一致会导致命中误差)
Cache-Control 常用指令(节选):
no-store:不存储响应(或等价于禁止进入缓存),最「干净」的禁用方式之一。no-cache:可以存,但使用缓存前必须先向源服务器做验证(走协商缓存/重新验证),不是「完全不缓存」。private/public:private表示响应仅供单个用户的私有缓存使用;public允许共享缓存(如 CDN)也缓存该响应。max-age=<秒>:从收到响应的时刻起算,在这么多秒内视为可用(与Expires相比不依赖绝对时钟)。must-revalidate:一旦超过缓存寿命,不得在未验证的情况下使用过期副本(与proxy-revalidate针对共享缓存不同)。
扩展指令(补充):
immutable:在max-age有效期内资源不会改变;配合带哈希的文件名(如app.abc123.js)可减少无意义的条件请求(浏览器支持度需结合实际版本验证)。stale-while-revalidate=<秒>:允许在过期后的宽限期内先返回旧内容,同时后台再验证更新(改善体感延迟)。stale-if-error=<秒>:源站错误时,在限定时间内仍可使用旧缓存(多见于 CDN/代理场景的配置思路)。
Pragma:
no-cache:效果和cache-control等no-cache一致。
1.3 Expires、Cache-Control、Pragma 的优先级
Pragma > Cache-Control > Expires
- 若存在
Cache-Control且包含明确的新鲜度信息(如max-age),通常max-age会优先生效,并覆盖同响应中的Expires(二者冲突时以Cache-Control为准)。 Pragma主要用于 HTTP/1.0 向后兼容;no-cache作为兜底方案
二、协商缓存(对比缓存 / 重新验证)
2.1 定义与现象
当本地副本过期或响应声明 no-cache(必须先验证) 时,浏览器仍会发起请求,但在请求头中携带缓存验证字段。若服务器判断资源未变,则返回 304 Not Modified,响应体通常为空,浏览器用本地缓存拼出最终资源。
2.2 两组验证头
(1)Last-Modified / If-Modified-Since
- 首次响应:
Last-Modified表示资源最后修改时间(精度一般为秒)。 - 再次请求:浏览器带上
If-Modified-Since(一般为上次的Last-Modified)。 - 服务器:若自该时间以来未修改 →
304;否则200并返回新内容与新的Last-Modified。
局限:秒级精度、某些场景下「改了文件但时间戳未变」或批量更新会导致误判风险;对动态资源也不够稳健。
(2)ETag / If-None-Match
- 首次响应:
ETag为资源的实体标签(常为哈希或版本标记,由服务器生成)。 - 再次请求:浏览器带上
If-None-Match(上次的ETag)。 - 服务器:若
ETag仍匹配 →304;否则200与新ETag。
优先级(常见实现):ETag 验证通常优先于 Last-Modified(二者同时存在时,服务端多以 If-None-Match 为准)。仍建议以具体服务端(Nginx/OSS/框架)行为为准。
小提示:
ETag在集群或多机部署时要注意生成算法一致,否则容易出现「同内容不同ETag」导致缓存命中率下降。
三、刷新方式对缓存的影响
下列行为对「强缓存 / 协商缓存是否仍可用」的典型归纳(以 Chromium 系与常见教材结论为主,以 DevTools Network 面板为准):
| 操作 | 强缓存 | 协商缓存 |
|---|---|---|
地址栏回车、<a> 跳转、新开窗口、前进/后退 | 通常有效 | 通常有效 |
| F5(普通刷新) | 通常绕过/失效 | 通常仍可通过验证命中 304 |
| Ctrl + F5(硬刷新,Mac 上多为 Cmd+Shift+R) | 通常失效 | 通常失效(可能带 Cache-Control: no-cache 等禁用语义) |
四、如何「尽量不走缓存」或强制重新验证
4.1 服务端(推荐)
- 使用
Cache-Control: no-store:不存或等价禁止缓存(最彻底之一)。 - 使用
Cache-Control: no-cache:可存,但每次使用前要验证。 - 组合示例(常见表述):
Cache-Control: no-cache, no-store, must-revalidate(按需求裁剪,避免无意义堆砌)。
4.2 客户端/调试手段(了解即可)
- 修改验证头(如
If-Modified-Since: 0、篡改If-None-Match)可能迫使服务端走不同分支——这属于调试或特殊客户端行为,生产页面不应依赖。 - 开发时更推荐:DevTools Disable cache、版本化文件名、query 版本号(注意 CDN 缓存键)、正确的响应头策略。
五、启发式缓存(Heuristic Caching)
当响应没有通过 Cache-Control: max-age 或 Expires 给出明确寿命时,浏览器可能根据规范允许的规则自行估算还能缓存多久。
5.1 与材料中公式的关系
材料中描述:在没有明确强缓存头时,可能参考 Last-Modified,用「当前时间与 Last-Modified 的间隔」乘以约 0.1 作为缓存时间。这与 RFC 7234 中的思路一致:当缺少显式过期时,缓存可以使用启发式新鲜度;若存在 Last-Modified,常用做法是取资源已存在时间的一定比例作为新鲜度寿命的估计——教材与文章里常概括为 10% 规则。