1. 先说一个很容易被忽略的事实:缓存不是“等浏览器自己搞定”的功能
做Web开发这些年,我见过太多团队把浏览器缓存当成一个“既重要又模糊”的底层层。大家都知道缓存能让页面更快、能省流量,但真到了线上出问题时,十有八九又都是缓存背锅:“用户看到的还是旧版本”“接口数据明明改了但客户端没更新”“强制刷新就好了”——这些话是不是特别耳熟?
我最早对缓存的理解也停留在“设置Cache-Control就能控制、改动文件名加个hash版本号就能破解更新”这个程度。直到一次线上事故让我彻底改变了对缓存的认知模式。
那次排查过程我到现在还记得很清楚:一个H5活动页上线后,服务端已经更新了页面里的一部分图片资源,但大概有30%的用户在打开页面时,仍然加载了旧图。从运维到前端排查了两三个小时,最后定位到根源是——图片请求的响应头里有一个我们压根没有主动设置过的 Expires 字段,而且这个字段的日期被设置成了整整一年之后。
当时我就明白了一个道理:你以为的“没设置缓存”,和浏览器实际执行的缓存策略,中间差了十万八千里。HTTP规范里有一套默认的缓存行为,如果你不懂这些底层规则,那么你写的每一个响应头、每一次资源更新,都可能是靠运气在跑。
这篇文章我就把我这些年关于缓存进阶机制和底层原理的积累做一个系统性的梳理,从协议层讲到浏览器实现层,再到日常开发中最容易踩的坑,一次性讲透。不管你是刚接触不久的前端新人,还是已经有好几年经验但一直靠“经验主义”控制缓存的开发者,这篇文章对应的知识深度应该都能帮到你。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 缓存真正的决策链条:不是后端说了算,也不是前端说了算
2.1 两个主角:强缓存与协商缓存的本质划分
很多人讲缓存喜欢直接列几个响应头:Cache-Control、Expires、ETag、Last-Modified,然后说“强缓存和协商缓存的区别”。但如果只是记结论,遇到真实请求时你大概率还是会懵。
你应该把缓存流程理解成一条决策链。当浏览器发起一个HTTP请求时,它首先会去本地缓存里找这个资源。找到了之后,它不会直接读,而是先做一套复杂的判断,判断结果无非两种:直接用本地缓存(强缓存命中),或者带着本地缓存的标识去服务端做一次“核对”,由服务端决定到底用新的还是用旧的(协商缓存命中或回源)。
强缓存的核心特征是“不需要发网络请求”。浏览器的DevTools里看到from disk cache或者from memory cache,就说明命中了强缓存。这种缓存形态下,浏览器可以完全不跟服务器说一句话,直接把本地副本拿出来用,因此性能最好,但信息也是最“旧”的。
协商缓存则不同,它必须“问一下服务器”,只不过问的方式很轻量——把上次响应时拿到的一个标识带过去(If-None-Match带ETag,If-Modified-Since带Last-Modified)。如果服务器比对之后认为资源没变,就返回一个304 Not Modified响应体为空的状态码,浏览器继续用本地副本。如果资源变了,服务端直接返回200和完整的新资源。
讲到这里,很多人会有一个误解,觉得“强缓存速度快,所以更好,协商缓存速度慢,所以差一些”。实际上两者不是性能优劣的关系,而是适用场景不同。强缓存适合那些“几乎肯定不变”的静态资源,比如版本化后的JS/CSS文件;协商缓存适合“可能变化”的资源,比如HTML页面本身。强制一个HTML页面使用一年的强缓存,看起来性能拉满,代价却是用户永远看到旧页面——这不是性能问题,这是事故。
2.2 响应头里的“时钟”和“钥匙”:Expires、Cache-Control、ETag是怎么协作的
聊完了决策链条,我们来拆具体的响应头。这四兄弟是缓存体系里最重要的四个角色,但它们的职责和关系经常被搞混。
先说Expires。它是HTTP/1.0时代的老前辈,用绝对时间表示缓存过期时间,例如Expires: Wed, 21 Oct 2026 07:28:00 GMT。它能用的前提是客户端本地时间和服务器时间足够一致。这是个很大的隐患——只要用户把电脑时间改慢一小时,或者设备时钟漂移,缓存的有效期就会发生变化。所以HTTP/1.1推出了Cache-Control,用相对时间解决这个问题。
Cache-Control: max-age=31536000表示“自从拿到这个响应那一刻起,往后一年内都有效”,完全不受客户端时钟影响。可以这样理解:Expires是一张写着“到2026年10月21日之前都可以用”的纸质票据,而Cache-Control是一张写着“从购买时刻起一年内有效”的电子凭证。后者显然更靠谱。
ETag则是服务器的“指纹印章”。每次资源内容有变化时,服务器会给这个新版本生成一个新的特征值。它可以是内容hash、版本号,也可以是任何能标识资源版本的值。配合ETag使用的Last-Modified则记录了资源的最后修改时间。两者都是协商缓存里用来“给服务器核对版本”的钥匙。如果服务器在比对中发现浏览器带过来的钥匙跟当前最新的不匹配,就会判定资源过期。
这里我想多提一句ETag和Last-Modified并存的场景。服务器一般在两者都能拿到时都会同时返回。实际核对时,ETag的优先级要比Last-Modified高,因为修改时间的最小精度是秒,如果同一秒内文件发生了多次改动,`Last-
