当我们在浏览器地址栏输入baidu.com并按下回车,页面加载的背后藏着一套精密的缓存机制 —— 它决定了资源是从本地快速读取,还是需要向服务器重新请求。Cache 缓存作为浏览器性能优化的核心手段,其设计逻辑贯穿了 URL 解析、请求发送、资源获取的全流程,本文将从底层细节拆解缓存的工作原理与实践逻辑。
一、缓存的核心价值:为何需要 “本地存储” 资源?
在 URL 输入到页面显示的链路中,网络请求是最大的性能瓶颈。以www.baidu.com/index.jsx这类静态资源为例,若每次加载页面都向服务器重新请求,会产生三方面问题:
- 服务器压力倍增:大量重复请求会占用服务器带宽与计算资源;
- 加载延迟显著:跨网络传输(尤其是弱网环境)会导致资源加载耗时变长,页面白屏时间增加;
- 带宽成本浪费:重复传输相同资源会消耗用户与服务端的额外带宽。
而缓存的本质是 “将已获取的资源暂存到本地”,当再次需要该资源时,优先从本地读取,跳过网络请求环节。据统计,合理的缓存策略可使页面加载速度提升 40% 以上,同时降低服务器 60% 的请求量。
二、强缓存:无需服务器交互的 “本地优先” 策略
强缓存是浏览器缓存的 “第一防线”—— 当用户再次请求资源时,浏览器会先检查本地缓存中是否存在该资源,以及资源是否 “过期”,若未过期则直接使用本地缓存,不向服务器发送任何请求。其判断依据来自 HTTP 响应头中的两个核心字段:Expires与Cache-Control。
1. HTTP 1.0 的遗留方案:Expires
Expires是 HTTP 1.0 定义的强缓存字段,本质是资源的 “过期时间戳” 。服务器在返回资源时,会在响应头中添加该字段,例如:
HTTP/1.0 200 OK
Content-Type: application/javascript
Expires: Wed, 30 Sep 2025 12:00:00 GMT
浏览器接收后,会将资源与该过期时间一起存储到本地缓存。当再次请求www.baidu.com/index.jsx时,浏览器会对比当前本地时间与Expires时间:
- 若本地时间 < Expires时间:强缓存命中,直接使用本地资源;
- 若本地时间 ≥ Expires时间:强缓存失效,进入后续协商缓存流程。
但Expires存在明显缺陷:依赖用户设备的本地时间。若用户手动修改设备时间(例如将时间调至Expires之后),会导致缓存判断失效 —— 明明资源未过期,却被误判为过期,进而触发不必要的服务器请求。
2. HTTP 1.1 的优化方案:Cache-Control
为解决Expires的时间依赖问题,HTTP 1.1 引入了Cache-Control字段,通过 “相对时间” 与 “缓存指令” 实现更精准的强缓存控制,优先级高于Expires(若两者同时存在,以Cache-Control为准)。
(1)核心指令:控制缓存的 “有效期” 与 “可存储范围”
Cache-Control支持多个指令组合,常见指令及含义如下:
| 指令 | 作用说明 |
|---|---|
| max-age=3600 | 资源的有效期为 3600 秒(1 小时),从资源获取成功时开始计时,而非本地时间 |
| public | 资源可被浏览器、CDN、代理服务器等 “所有中间节点” 缓存(适用于公开静态资源) |
| private | 资源仅可被浏览器本地缓存,不可被中间节点缓存(适用于用户个性化资源) |
| no-store | 完全不缓存资源(适用于敏感数据,如用户登录态、支付信息) |
| no-cache | 不使用强缓存,但可进入协商缓存(需向服务器校验资源新鲜度) |
例如,百度首页的 JS 资源响应头可能如下:
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: public, max-age=86400
这意味着:该资源是公开的,可被浏览器与 CDN 缓存,有效期为 24 小时(86400 秒)。24 小时内再次请求该资源,浏览器直接从本地读取,无需与服务器交互。
(2)强缓存命中的关键:“资源标识 + 缓存状态” 匹配
浏览器判断强缓存时,会以 “完整 URL(协议 + 域名 + 端口 + 路径 + 参数)” 作为资源唯一标识,同时检查缓存中的Cache-Control状态:
- 若标识匹配,且max-age未过期:强缓存命中,直接加载本地资源;
- 若标识不匹配(如 URL 参数变化),或max-age已过期:强缓存失效,进入协商缓存流程。
三、协商缓存:服务器主导的 “新鲜度校验” 机制
当强缓存失效(资源过期或无强缓存配置)时,浏览器不会直接丢弃本地缓存,而是会向服务器发送 “校验请求”,由服务器判断资源是否 “新鲜”(即是否有更新)—— 这一过程称为协商缓存。若资源未更新,服务器返回304 Not Modified,浏览器继续使用本地缓存;若资源已更新,服务器返回200 OK与新资源,并更新本地缓存。
协商缓存的判断依据同样来自 HTTP 头,分为两组核心字段:Last-Modified/If-Modified-Since与ETag/If-None-Match。
1. 时间维度校验:Last-Modified与If-Modified-Since
(1)工作流程
- 首次请求:服务器返回资源时,在响应头中添加Last-Modified,表示资源的 “最后修改时间”:
HTTP/1.1 200 OK
Content-Type: image/png
Last-Modified: Tue, 25 Aug 2025 10:30:00 GMT
浏览器存储资源时,会同步保存Last-Modified值。
- 再次请求:强缓存失效后,浏览器在请求头中添加If-Modified-Since,值为上次保存的Last-Modified:
GET /logo.png HTTP/1.1
Host: www.baidu.com
If-Modified-Since: Tue, 25 Aug 2025 10:30:00 GMT
3. 服务器校验:服务器对比请求头的If-Modified-Since与资源当前的最后修改时间:
-
- 若资源未修改(时间一致):返回304 Not Modified,不携带资源体,浏览器使用本地缓存;
-
- 若资源已修改(时间不一致):返回200 OK与新资源,并更新Last-Modified。
(2)缺陷:精度与逻辑局限
Last-Modified的校验逻辑依赖 “修改时间”,但存在两类场景会导致误判:
- 资源内容未变,但修改时间被篡改(如服务器重启导致文件时间更新);
- 资源修改时间的精度仅为 “秒级”,若 1 秒内资源被多次修改,无法区分。
2. 内容维度校验:ETag与If-None-Match
为解决Last-Modified的精度问题,HTTP 引入了ETag(Entity Tag)—— 资源的 “唯一内容标识”,通常由服务器对资源内容进行哈希计算(如 MD5、SHA-1)生成,若资源内容变化,ETag会随之改变。
(1)工作流程
- 首次请求:服务器返回资源时,在响应头中添加ETag,表示资源的内容哈希:
HTTP/1.1 200 OK
Content-Type: text/css
ETag: "5f8d72a3-128c" # 哈希值示例
浏览器存储资源时,同步保存ETag值。
- 再次请求:强缓存失效后,浏览器在请求头中添加If-None-Match,值为上次保存的ETag:
GET /style.css HTTP/1.1
Host: www.baidu.com
If-None-Match: "5f8d72a3-128c"
3. 服务器校验:服务器对比请求头的If-None-Match与资源当前的ETag:
-
- 若ETag一致(内容未变):返回304 Not Modified,浏览器使用本地缓存;
-
- 若ETag不一致(内容已变):返回200 OK与新资源,并更新ETag。
(2)优势与权衡
ETag的核心优势是基于内容校验,精度远高于时间维度,可解决Last-Modified的误判问题。但缺点是服务器计算开销增加—— 每次生成ETag需对资源内容进行哈希计算,对于大文件或高并发场景,可能影响服务器性能。
实际项目中,通常会根据资源类型选择:静态小文件(如 CSS、JS)用ETag,静态大文件(如视频、压缩包)用Last-Modified,平衡精度与性能。
四、缓存与 HTTP 跳转:3xx 状态码的缓存逻辑
在 URL 解析流程中,常遇到 “协议跳转”(如http://转https://)或 “路径跳转”,此时服务器会返回 3xx 状态码,而这些跳转本身也可能被缓存,影响后续请求链路。
1. 常见跳转状态码与缓存特性
| 状态码 | 含义 | 缓存特性 | 适用场景 |
|---|---|---|---|
| 301 | 永久重定向 | 浏览器会缓存跳转关系,下次请求原 URL 时,直接跳转到目标 URL,无需向服务器确认 | 域名迁移(如old.com→new.com) |
| 302 | 临时重定向 | 默认不缓存跳转关系,每次请求原 URL 都需服务器返回跳转指令(部分浏览器可能缓存) | 临时维护、A/B 测试 |
| 307 | 临时重定向(保留方法) | 不缓存跳转关系,且严格保留原请求方法(如 POST 请求不会转为 GET) | HTTP→HTTPS 临时跳转 |
| 308 | 永久重定向(保留方法) | 缓存跳转关系,且保留原请求方法 | 需保留 POST 方法的永久跳转 |
2. 典型场景:HTTP 到 HTTPS 的跳转缓存
当用户输入www.baidu.com时,服务器会返回 307 跳转响应:
HTTP/1.1 307 Temporary Redirect
Location: https://www.baidu.com
浏览器接收后,会重新向www.baidu.com发送请求。由于 307 是 “临时跳转”,浏览器不会缓存该跳转关系 —— 下次用户再输入www.baidu.com,仍需先请求原地址,再接收跳转指令。
若服务器返回 301(永久跳转),则浏览器会缓存 “www.baidu.com→www.baidu.com” 的映射关系,下次输入http://地址时,直接跳转到https://,跳过原地址请求,进一步提升速度。
五、缓存的 “边界”:域名、端口与存储位置
缓存并非 “全局通用”,而是存在明确的 “边界”,这些边界由 URL 结构与浏览器存储规则共同决定。
1. 域名与端口:缓存的 “隔离单位”
浏览器以 “协议 + 域名 + 端口” 作为缓存的隔离维度 —— 即使是同一域名,不同端口或协议的资源,缓存也是完全独立的。例如:
- www.baidu.com:80/index.js(HTTP 协议,80 端口)
- www.baidu.com:443/index.js(HTTPS 协议,443 端口)
- www.baidu.com:3000/index.js(HTTP 协议,3000 端口)
这三个 URL 对应的资源会被视为 “三个不同的缓存项”,互不干扰。原因是 “同一主机的不同端口对应不同服务”(如 80 端口可能是 Nginx 代理,3000 端口是 Node.js 服务),资源内容可能完全不同。
同时,浏览器会自动 “补全 URL” 以优化体验 —— 例如用户输入baidu.com,浏览器会自动补全为www.baidu.com,补全后的 URL 才是缓存的实际标识,避免因输入不完整导致重复请求。
2. 存储位置:内存缓存 vs 磁盘缓存
浏览器的缓存分为两类存储位置,各有特点:
| 存储位置 | 优点 | 缺点 | 适用资源类型 |
|---|---|---|---|
| 内存缓存 | 读取速度极快(毫秒级) | 关闭浏览器后缓存失效 | 小体积、高频使用资源(如 JS 变量、小图片) |
| 磁盘缓存 | 持久化存储(重启浏览器仍存在) | 读取速度较慢(毫秒 - 秒级) | 大体积、低频使用资源(如 CSS 文件、视频、大图片) |
浏览器会根据资源大小与使用频率自动选择存储位置:例如index.js(小体积,每次页面加载都需使用)会存入内存缓存,banner.png(大体积,仅首页使用)会存入磁盘缓存。
六、缓存最佳实践:平衡性能与新鲜度
缓存的核心矛盾是 “性能” 与 “新鲜度”—— 缓存时间越长,性能越好,但资源更新后用户可能看到旧内容。以下是行业通用的最佳实践:
1. 静态资源:强缓存为主,版本控制为辅
对于 JS、CSS、图片等静态资源,采用 “Cache-Control: public, max-age=31536000”(缓存 1 年),同时通过 “版本号” 或 “哈希值” 实现资源更新,例如:
- 更新后资源:www.baidu.com/index.v2.js(版本号)或www.baidu.com/index.5f8d7…(哈希值)
当资源更新时,URL 会发生变化,浏览器会将其视为 “新资源”,重新请求并缓存,既保证了长期缓存的性能,又避免了旧内容问题。
2. 动态资源:协商缓存为主,禁用强缓存
对于 API 接口(如www.baidu.com/api/user)、HTML 页面等动态资源,内容可能实时变化,需禁用强缓存,仅使用协商缓存:
# 响应头配置
Cache-Control: no-cache # 禁用强缓存,进入协商缓存
ETag: "user-12345" # 基于用户数据生成ETag
这样既能通过协商缓存减少重复请求(返回 304),又能保证资源更新时用户能及时获取最新内容。
3. 敏感资源:完全禁用缓存
对于用户登录态、支付信息、验证码等敏感资源,需完全禁用缓存,避免信息泄露:
Cache-Control: no-store, no-cache
Pragma: no-cache # 兼容HTTP 1.0
no-store表示 “不存储任何缓存”,Pragma: no-cache是 HTTP 1.0 的兼容字段,确保旧浏览器也能禁用缓存。
总结
Cache 缓存是浏览器性能优化的 “基石”,从强缓存的 “本地优先” 到协商缓存的 “服务器校验”,从跳转状态码的缓存逻辑到存储位置的选择,每一个细节都围绕 “减少网络请求、提升加载速度” 展开。理解缓存的底层原理,不仅能帮助我们排查 “资源不更新”“加载慢” 等问题,更能设计出兼顾性能与新鲜度的缓存策略,为用户提供更流畅的页面体验。