核心目标: 浏览器缓存的核心目标是减少网络请求的数量和体积,从而:
- 提升页面加载速度:从本地读取资源远比从网络下载快得多。
- 降低服务器压力:减少不必要的请求。
- 节省用户流量:避免重复下载相同资源。
浏览器缓存策略概览:
当浏览器需要请求一个资源(如HTML、CSS、JS、图片)时,它会按照以下流程决策:
-
检查强缓存: 首先检查这个资源是否在本地缓存中并且是否过期。
- 如果有效(未过期) :浏览器直接使用本地缓存的资源,完全不发送任何HTTP请求到服务器。状态码表现为
200 OK (from disk cache)或200 OK (from memory cache)。 - 如果无效(已过期) :则进入下一步 - 协商缓存。
- 如果有效(未过期) :浏览器直接使用本地缓存的资源,完全不发送任何HTTP请求到服务器。状态码表现为
-
进行协商缓存: 浏览器知道本地有缓存,但不确定是否最新。它会携带特定的验证信息(缓存标识)向服务器发起一个请求。
- 如果服务器判断资源未修改:返回
304 Not Modified状态码,不返回资源内容。浏览器收到304后,直接使用本地缓存的资源。 - 如果服务器判断资源已修改:返回
200 OK状态码,并返回最新的资源内容和新的缓存标识。浏览器使用新资源并更新本地缓存。
- 如果服务器判断资源未修改:返回
现在,我们深入强缓存和协商缓存:
一、强缓存 (Strong Cache / Local Cache)
-
核心思想: 完全不与服务器通信。浏览器根据自己的缓存规则,判断资源是否在有效期内。如果在,就直接用。
-
控制字段: 主要通过HTTP响应头设置:
-
Expires(HTTP/1.0) :- 值是一个绝对时间 (GMT格式),例如
Expires: Wed, 25 Jul 2025 08:00:00 GMT。 - 缺点:依赖客户端时间。如果用户修改了本地时间,会导致缓存判断错误。
- 优先级低于
Cache-Control。
- 值是一个绝对时间 (GMT格式),例如
-
Cache-Control(HTTP/1.1) :-
值是一个或多个指令,更灵活、更常用,优先级更高。
-
关键指令:
max-age=: 资源的最大有效时间(单位:秒)。例如Cache-Control: max-age=3600表示资源在1小时内有效。这是一个相对时间,从资源被请求成功的时间开始计算,不依赖客户端时间。public:响应可以被任何对象(客户端、代理服务器等)缓存。private:响应只能被单个用户(浏览器)缓存,不能被代理服务器等共享缓存。no-cache:不使用强缓存。每次使用缓存前必须向服务器发起协商验证请求(走协商缓存流程)。名字有点误导,它实际是“跳过强缓存,进入协商缓存”。no-store:完全不缓存。每次请求都直接从服务器获取完整响应。最严格的策略。immutable(非标准但广泛支持):表示资源永不过期(在用户主动刷新前)。适用于带指纹(hash)的资源,明确告诉浏览器这个文件内容永远不会变,即使强制刷新(F5)也优先使用缓存(需要Ctrl+F5或清除缓存才能更新)。提升刷新体验。s-maxage=:针对共享缓存(如CDN、代理服务器)设置的有效期,优先级高于max-age。
-
-
-
强缓存生效时的表现:
- Chrome DevTools Network Tab 中:状态码显示
200 OK,并在Size列显示(disk cache)或(memory cache)。 - 没有网络请求发出! 这是性能提升的关键。
- Chrome DevTools Network Tab 中:状态码显示
二、协商缓存 (Conditional Cache / Validation Cache)
-
核心思想: 需要与服务器通信验证。浏览器携带缓存的“标识”询问服务器:“我上次存的这个版本的资源,现在还能用吗?” 服务器根据标识判断资源是否改变。
-
触发条件:
- 强缓存失效(
Expires/max-age过期)。 - 响应头设置了
Cache-Control: no-cache(跳过强缓存,直接协商)。
- 强缓存失效(
-
控制字段: 涉及两组HTTP头:
-
请求头 (由浏览器发送):
If-None-Match: 其值是上次响应中服务器返回的ETag值。例如If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"。If-Modified-Since: 其值是上次响应中服务器返回的Last-Modified值。例如If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT。
-
响应头 (由服务器返回):
ETag(Entity Tag):资源的唯一标识符(通常是内容的哈希值或版本号)。更精确,能感知内容字节级别的变化(即使修改时间没变)。例如ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"。Last-Modified:资源在服务器上最后修改的时间(GMT格式)。精度是秒级,如果文件修改非常快(毫秒内多次修改),可能无法准确判断。例如Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT。
-
-
协商缓存流程:
-
浏览器发现强缓存失效或需要协商。
-
浏览器准备请求:
- 如果本地缓存有
ETag,则在请求头中添加If-None-Match。 - 如果本地缓存有
Last-Modified,则在请求头中添加If-Modified-Since。两者可能同时存在。
- 如果本地缓存有
-
服务器收到请求:
- 比较
If-None-Match和服务器计算出的当前资源的ETag。 - 或者比较
If-Modified-Since和服务器上资源的最后修改时间。 - 注意:
ETag的优先级通常高于Last-Modified。 服务器可以同时使用两者,但更倾向于使用ETag进行精确验证。
- 比较
-
服务器响应:
-
如果资源未修改 (
ETag匹配 或Last-Modified时间相同):- 返回
304 Not Modified状态码。 - 不返回资源内容(节省带宽)。
- 可以更新响应头中的
Cache-Control/Expires/ETag/Last-Modified等(可选,用于更新客户端缓存的有效期)。
- 返回
-
如果资源已修改 (
ETag不匹配 或Last-Modified时间不同):- 返回
200 OK状态码。 - 返回完整的、最新的资源内容。
- 在响应头中携带新的
Cache-Control/Expires/ETag/Last-Modified。
- 返回
-
-
浏览器收到响应:
- 如果是
304:直接使用本地缓存的资源。 - 如果是
200:使用新的资源,并根据响应头更新本地缓存。
- 如果是
-
-
协商缓存生效时的表现:
- Chrome DevTools Network Tab 中:状态码显示
304 Not Modified。 - 有网络请求发出,但没有资源内容传输(只有HTTP头),因此比强缓存慢,但比完整下载新资源快得多。
- Chrome DevTools Network Tab 中:状态码显示
三、缓存位置 (Browser Cache Storage)
浏览器缓存通常存储在两个地方:
-
Memory Cache (内存缓存):
- 存储当前会话中频繁访问的小资源(如当前页面的CSS、JS、图片)。
- 访问速度最快。
- 关闭浏览器标签页或进程后,缓存通常会被清除(取决于浏览器实现)。
- DevTools Size列显示
(memory cache)。
-
Disk Cache (磁盘缓存):
- 存储持久化的、较大的资源(如非当前页面的资源、大图片、字体文件)。
- 容量更大,但访问速度比内存缓存慢。
- 关闭浏览器后缓存通常保留(遵循
Cache-Control/Expires的设置)。 - DevTools Size列显示
(disk cache)。
浏览器会根据资源大小、使用频率、缓存策略等自动决定资源存放在内存还是磁盘。开发者一般无法直接控制具体位置。
四、用户操作对缓存的影响
-
正常浏览 (输入URL, 链接跳转): 浏览器会严格遵守缓存策略(强缓存 -> 协商缓存)。
-
普通刷新/页面内刷新 (F5 / 按钮 /
location.reload()):- 浏览器通常会跳过强缓存(视为
Cache-Control: max-age=0或no-cache)。 - 进行协商缓存(发送
If-None-Match/If-Modified-Since)。 Cache-Control: immutable的资源可能例外。
- 浏览器通常会跳过强缓存(视为
-
强制刷新/硬刷新 (Ctrl+F5 / Cmd+Shift+R /
location.reload(true)):- 浏览器忽略所有缓存(强缓存和协商缓存)。
- 请求头中会添加
Cache-Control: no-cache和Pragma: no-cache,并且不会发送If-None-Match/If-Modified-Since。 - 服务器必须返回
200 OK和完整的新资源。
-
清空缓存并硬刷新 (DevTools 功能): 清除本地缓存后再强制刷新。
五、最佳实践建议
-
区分资源类型设置缓存策略:
- 带指纹/哈希的资源 (如
main.abcd1234.js,style.efgh5678.css): 内容改变,URL必然改变。设置超长强缓存 (如Cache-Control: max-age=31536000, immutable)。用户获取新版本资源完全依赖URL改变。 - 不带指纹但频繁变化的资源 (如
index.html, 入口文件): 设置Cache-Control: no-cache或很短的max-age(如几秒),配合ETag/Last-Modified走协商缓存。确保用户能及时获取更新。 - 几乎不变的静态资源 (如老版本库): 可以设置较长强缓存 (如几周或几个月)。
- 敏感数据/API响应: 根据业务需求谨慎设置,通常
private和较短的max-age或no-store。
- 带指纹/哈希的资源 (如
-
优先使用
Cache-Control: 比Expires更灵活、更可靠(不依赖客户端时间)。 -
优先使用
ETag: 比Last-Modified更精确(能感知内容不变仅修改时间变,或快速连续修改)。 -
利用
immutable: 对带哈希的资源非常有效,能显著改善刷新体验。 -
避免滥用
no-store: 只在绝对必要时使用,否则会失去缓存的性能优势。 -
CDN配置: 确保CDN正确理解并转发
Cache-Control、ETag等头部,并配置好自身的缓存规则。
总结流程图
text
浏览器请求资源
|
v
+---------------------+
| 检查强缓存是否有效? | <---- (检查 Cache-Control / Expires)
| (本地缓存 + 未过期) |
+---------------------+
|
| 有效
v
+---------------------+ +---------------------+
| 使用本地缓存 | | |
| 200 (from cache) | | |
+---------------------+ | |
| | |
| 无效 | |
v | |
+---------------------+ | |
| 发送协商缓存请求 | --------> | 服务器验证 |
| (If-None-Match | | (ETag / |
| If-Modified-Since) | | Last-Modified) |
+---------------------+ | |
| | |
| | 未修改 | 已修改
| v v
| +---------------------+ +---------------------+
| | 返回 304 Not Modified| | 返回 200 OK + 新资源|
| +---------------------+ +---------------------+
| | |
| | |
+<--------------------+ |
| 使用本地缓存 | 使用新资源并更新缓存
v v
理解并合理应用强缓存和协商缓存,是优化Web应用加载速度和用户体验的关键步骤。务必根据资源的实际更新频率和特性选择合适的策略。