浏览器的缓存机制(强缓存 vs 协商缓存)
浏览器的缓存机制是前端性能优化的核心内容之一。简单来说,浏览器缓存就是把一个已经请求过的资源(如 HTML、CSS、JS、图片)拷贝一份副本存储在本地,当下次需要该资源时,根据特定规则决定是直接使用副本,还是向服务器重新请求。
浏览器的缓存机制主要分为两类:强缓存(Strong Cache) 和 协商缓存(Negotiated Cache)。
一、 核心流程图解
在深入细节之前,先建立一个整体的决策流程:
- 浏览器发起请求。
- 检查强缓存:
- 命中?直接使用本地缓存(状态码 200,显示
from memory cache或from disk cache)。 - 未命中(或已过期)?进入下一步。
- 命中?直接使用本地缓存(状态码 200,显示
- 检查协商缓存(向服务器发送请求):
- 服务器对比后发现资源未变?返回 304 Not Modified,浏览器读取本地缓存。
- 服务器对比后发现资源变了?返回 200 OK 和新的资源内容,浏览器更新本地缓存。
二、 强缓存 (Strong Cache)
核心概念:浏览器直接判断本地缓存是否过期。如果没过期,完全不跟服务器通信,直接使用本地文件。
控制字段(HTTP Header):
强缓存主要由 Expires 和 Cache-Control 两个字段控制。
1. Expires (HTTP/1.0)
- 值:一个绝对的服务器时间(例如:
Wed, 22 Oct 2025 08:41:00 GMT)。 - 原理:浏览器拿当前系统时间跟这个时间对比,如果当前时间小于 Expires,则缓存有效。
- 缺点:如果客户端时间被修改,或者客户端与服务器时间不一致,会导致判断错误。现在基本已被 Cache-Control 取代。
2. Cache-Control (HTTP/1.1) - 优先级更高
- 值:相对时间或其他指令。
- 常见指令:
max-age=3600:表示资源在 3600 秒(1小时)内是有效的。这是最常用的。no-cache:注意! 这不代表不缓存,而是代表跳过强缓存,直接进入协商缓存阶段(每次都要向服务器确认)。no-store:真正的“不缓存”。任何地方都不存副本,每次都从服务器拿最新的。public:客户端和代理服务器(CDN)都可以缓存。private:只有客户端可以缓存(默认值)。
三、 协商缓存 (Negotiated Cache)
核心概念:强缓存失效(过期)或者被设置为 no-cache 后,浏览器携带缓存标识向服务器发起请求,由服务器来决定缓存是否可用。
控制字段(成对出现):
协商缓存依赖于两组字段,一组是基于时间的,一组是基于内容的。
1. Last-Modified / If-Modified-Since (基于时间)
- Last-Modified (响应头):服务器告诉浏览器,这个资源最后修改的时间。
- If-Modified-Since (请求头):当浏览器再次请求该资源时,会把上次收到的
Last-Modified的值放在这里发给服务器。 - 服务器逻辑:对比
If-Modified-Since和服务器上文件的最后修改时间。- 如果一致:返回 304(资源没变,用缓存)。
- 如果不一致:返回 200 + 新资源 + 新的
Last-Modified。
- 缺点:
- 精度问题:只能精确到秒。如果 1 秒内修改了多次,它感知不到。
- 内容未变时间变:有时候文件只是被“摸”了一下(保存了一下),内容没变但时间变了,会导致缓存失效。
2. ETag / If-None-Match (基于内容摘要) - 优先级更高
- ETag (响应头):服务器根据文件内容生成的唯一标识(类似哈希值/指纹)。
- If-None-Match (请求头):浏览器再次请求时,把上次收到的
ETag发给服务器。 - 服务器逻辑:对比
If-None-Match和当前文件的ETag。- 一致:返回 304。
- 不一致:返回 200 + 新资源 + 新的
ETag。
- 优点:非常精准,只要内容变了 ETag 就会变。
- 缺点:服务器计算 ETag 需要消耗一定的性能。
四、 总结与对比
| 特性 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否向服务器发请求 | 否 (直接由浏览器拦截) | 是 (发请求询问) |
| HTTP 状态码 | 200 (from cache) | 304 (Not Modified) 或 200 |
| 主要 Header | Cache-Control (max-age), Expires |
ETag, Last-Modified |
| 谁来决定 | 浏览器根据 Header 自动决定 | 服务器对比后决定 |
| 优先级 | 最高 | 强缓存失效后才触发 |
Header 优先级排序:
Cache-Control>ExpiresETag>Last-Modified
五、 最佳实践策略
在现代前端工程化(如 Webpack/Vite 打包)中,通常采用以下缓存策略:
HTML 文件:
- 使用 协商缓存 (
Cache-Control: no-cache)。 - 原因:HTML 是入口,必须保证用户拿到的是最新的引用。如果 HTML 被强缓存锁死,用户就永远拿不到更新后的 CSS/JS 文件名。
- 使用 协商缓存 (
静态资源 (CSS/JS/图片):
- 使用 强缓存 (
Cache-Control: max-age=31536000),设置一个超长的过期时间(如一年)。 - 配合 Hash 文件名:利用构建工具给文件名加上 Hash(如
main.a1b2c3.js)。 - 原因:一旦内容修改,Hash 变化,文件名就变了,HTML 会引用新的 URL,自然就请求新文件了;如果内容没变,文件名不变,直接利用强缓存,速度极快。
- 使用 强缓存 (
六、 用户行为对缓存的影响
- 地址栏回车 / 点击链接 / 前进后退:
- 强缓存有效,协商缓存有效。
- F5 刷新 (普通刷新):
- 强缓存失效 (浏览器会把
max-age设为 0 或加上no-cache)。 - 协商缓存有效 (会带上
If-Modified-Since/If-None-Match询问服务器)。
- 强缓存失效 (浏览器会把
- Ctrl + F5 (强制刷新):
- 所有缓存失效。浏览器不带任何缓存标识,直接请求最新资源 (Header 中会带
Cache-Control: no-cache和Pragma: no-cache)。
- 所有缓存失效。浏览器不带任何缓存标识,直接请求最新资源 (Header 中会带