浏览器缓存深度解析:强缓存、协商缓存与 URL 链路中的缓存策略

132 阅读12分钟

当我们在浏览器地址栏输入baidu.com并按下回车,页面加载的背后藏着一套精密的缓存机制 —— 它决定了资源是从本地快速读取,还是需要向服务器重新请求。Cache 缓存作为浏览器性能优化的核心手段,其设计逻辑贯穿了 URL 解析、请求发送、资源获取的全流程,本文将从底层细节拆解缓存的工作原理与实践逻辑。

image.png

一、缓存的核心价值:为何需要 “本地存储” 资源?

在 URL 输入到页面显示的链路中,网络请求是最大的性能瓶颈。以www.baidu.com/index.jsx这类静态资源为例,若每次加载页面都向服务器重新请求,会产生三方面问题:

  1. 服务器压力倍增:大量重复请求会占用服务器带宽与计算资源;
  1. 加载延迟显著:跨网络传输(尤其是弱网环境)会导致资源加载耗时变长,页面白屏时间增加;
  1. 带宽成本浪费:重复传输相同资源会消耗用户与服务端的额外带宽。

而缓存的本质是 “将已获取的资源暂存到本地”,当再次需要该资源时,优先从本地读取,跳过网络请求环节。据统计,合理的缓存策略可使页面加载速度提升 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)工作流程

  1. 首次请求:服务器返回资源时,在响应头中添加Last-Modified,表示资源的 “最后修改时间”:
HTTP/1.1 200 OK
Content-Type: image/png
Last-Modified: Tue, 25 Aug 2025 10:30:00 GMT

浏览器存储资源时,会同步保存Last-Modified值。

  1. 再次请求:强缓存失效后,浏览器在请求头中添加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)工作流程

  1. 首次请求:服务器返回资源时,在响应头中添加ETag,表示资源的内容哈希:
HTTP/1.1 200 OK
Content-Type: text/css
ETag: "5f8d72a3-128c"  # 哈希值示例

浏览器存储资源时,同步保存ETag值。

  1. 再次请求:强缓存失效后,浏览器在请求头中添加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.comnew.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.comwww.baidu.com” 的映射关系,下次输入http://地址时,直接跳转到https://,跳过原地址请求,进一步提升速度。

五、缓存的 “边界”:域名、端口与存储位置

缓存并非 “全局通用”,而是存在明确的 “边界”,这些边界由 URL 结构与浏览器存储规则共同决定。

1. 域名与端口:缓存的 “隔离单位”

浏览器以 “协议 + 域名 + 端口” 作为缓存的隔离维度 —— 即使是同一域名,不同端口或协议的资源,缓存也是完全独立的。例如:

这三个 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 年),同时通过 “版本号” 或 “哈希值” 实现资源更新,例如:

当资源更新时,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 缓存是浏览器性能优化的 “基石”,从强缓存的 “本地优先” 到协商缓存的 “服务器校验”,从跳转状态码的缓存逻辑到存储位置的选择,每一个细节都围绕 “减少网络请求、提升加载速度” 展开。理解缓存的底层原理,不仅能帮助我们排查 “资源不更新”“加载慢” 等问题,更能设计出兼顾性能与新鲜度的缓存策略,为用户提供更流畅的页面体验。