2、浏览器缓存机制:强缓存与协商缓存、Cache-Control与Expires、ETag与Last-Modified

聊图片复用,绕不开浏览器缓存。说白了,缓存就是浏览器帮你省流量的「记忆体」。你想想看,用户从页面A跳到页面B,如果同一张图片每次都要重新请求,那体验得多糟糕。我早期做项目时,就吃过不懂缓存的亏——明明图片没变,却让用户白白浪费流量。

浏览器缓存分两种:强缓存和协商缓存。它们不是二选一的关系,而是配合使用的。先看强缓存,它最霸道——只要没过期,连请求都不发。

2.1 强缓存:浏览器说了算

强缓存的核心逻辑很简单:浏览器判断资源还在有效期内,就直接从本地读取,HTTP状态码返回200(from disk cache 或 from memory cache)。整个过程不跟服务器打招呼。

控制强缓存有两个老牌字段:Expires 和 Cache-Control。

2.1.1 Expires:HTTP/1.0 的遗产

Expires 是一个绝对时间戳,比如:

Expires: Thu, 31 Dec 2025 23:59:59 GMT

浏览器拿到这个时间,跟本地时钟对比。没到?用缓存。到了?发请求。

坑在哪? 依赖客户端时间。用户改了系统时间,缓存就乱套了。我曾经排查过一个线上问题:用户反馈图片一直不更新,结果发现他电脑时间被调慢了一年。Expires 直接失效。

2.1.2 Cache-Control:现代标准

Cache-Control 是 HTTP/1.1 引入的,用相对时间控制缓存。常用指令:

指令 含义 示例
max-age 资源可缓存的最大秒数 max-age=31536000
no-cache 强制走协商缓存(不是不缓存) no-cache
no-store 完全不缓存 no-store
public 任何中间节点都可缓存 public, max-age=86400
private 仅浏览器可缓存 private, max-age=86400

我个人习惯:能上 Cache-Control 就别用 Expires。如果非要兼容老系统,两个一起写——Cache-Control 优先级更高。

经验之谈: 图片资源通常设置 max-age=31536000(一年)。配合文件名哈希(比如 logo.a1b2c3d4.png),既能长期缓存,又能精确更新。

2.2 协商缓存:跟服务器商量一下

强缓存过期了,不代表资源一定变了。这时候轮到协商缓存出场。浏览器带着「凭证」去问服务器:这个资源还新鲜吗?服务器说「没变」,返回304状态码,浏览器继续用缓存。

协商缓存有两组搭档:Last-Modified / If-Modified-Since 和 ETag / If-None-Match。

2.2.1 Last-Modified / If-Modified-Since

服务器返回资源时,带上最后修改时间:

Last-Modified: Wed, 15 Mar 2025 10:30:00 GMT

浏览器下次请求时,带上这个时间:

If-Modified-Since: Wed, 15 Mar 2025 10:30:00 GMT

服务器对比时间。没变?返回304。变了?返回200+新资源。

注意: Last-Modified 精度只到秒。如果资源在1秒内被多次修改,它检测不到。另外,某些CDN或代理服务器可能会修改文件时间戳,导致缓存失效。

2.2.2 ETag / If-None-Match

ETag 是资源的唯一标识符,通常由文件内容哈希生成。它比时间戳更精确:

ETag: "abc123def456"

浏览器下次请求时带上:

If-None-Match: "abc123def456"

服务器对比ETag。匹配?304。不匹配?200+新资源。

我建议:ETag 优先于 Last-Modified。如果服务器同时返回两者,浏览器会同时使用——ETag 的优先级更高。

避坑指南: 我曾经遇到一个案例,后端用文件修改时间生成 ETag,结果多台服务器时间不同步,导致同一资源在不同节点上 ETag 不一致。用户反复下载同一张图片。解决方案:用文件内容的 MD5 哈希生成 ETag。

2.3 缓存决策流程

浏览器拿到一个图片资源,到底走哪条路?我画了一张流程图,帮你理清逻辑:

浏览器缓存决策流程图 发起图片请求 强缓存是否有效? (检查 max-age / Expires) 是 使用本地缓存 状态码 200 (from cache) 否 协商缓存是否命中? (ETag / Last-Modified 对比) 是 返回 304 使用缓存 否 返回 200 + 新资源 渲染图片到页面 💡 关键点: 强缓存命中 → 不发请求,速度最快 协商缓存命中 → 发一次请求,返回304,比重新下载快

2.4 图片缓存的实战配置

回到我们的课程主题——图片资源在页面切换中的复用。我推荐一套经过验证的配置方案:

场景 Cache-Control 协商缓存 说明
静态图片(logo、图标) public, max-age=31536000, immutable ETag(内容哈希) 一年不换,文件名带哈希
用户头像 private, max-age=86400 ETag + Last-Modified 一天缓存,用户专属
轮播图(可能更新) public, max-age=3600 ETag 一小时强缓存,之后协商
背景大图 public, max-age=604800 Last-Modified 一周缓存,节省带宽
小技巧: 在 Cache-Control 中加入 immutable 指令(Chrome 和 Firefox 支持),告诉浏览器:这个资源在过期前绝对不会变。这样即使用户手动刷新页面,浏览器也不会发起协商缓存请求。

2.5 常见问题与避坑

做图片缓存,有几个坑我踩过,分享给你:

  • 缓存更新不及时:用户看到的还是旧图片。解决方案:文件名加版本哈希,比如 banner.v2.png。不要只依赖缓存过期。
  • CDN 缓存不一致:不同节点缓存不同版本。解决方案:统一 ETag 生成策略,确保所有节点对同一资源返回相同 ETag。
  • HTTPS 与缓存代理:某些代理服务器会剥离 Cache-Control。解决方案:在响应头中同时保留 Expires 作为降级方案。
  • Service Worker 干扰:如果用了 Service Worker,它可能接管缓存策略。解决方案:明确 SW 中的缓存逻辑,避免与 HTTP 缓存冲突。