
面试官问:"浏览器缓存策略有哪些?"
你熟练地背诵:"强缓存用 Cache-Control,协商缓存用 ETag 和 Last-Modified..."
面试官点点头,接着问:"那我在地址栏输入 URL 回车,和按 F5 刷新,这两种情况下,缓存策略生效有什么区别?"

这一问,把很多只背了 HTTP 头定义的候选人问住了。
"难道...不都是发请求吗?"
当然不是。用户行为会直接改变浏览器对缓存头的处理方式。 这篇文章就帮你把这个隐蔽的知识点挖透。
为了搞清这个问题,我们不能只看服务器发了什么头,还得看用户怎么操作。浏览器把用户行为分成了三个等级:
当你在地址栏输入 URL 回车,或者点击页面跳转链接时,浏览器会表现得"非常懒"。
它会优先看本地有没有强缓存(Cache-Control: max-age=xxx)。
200 (from disk cache) 或 200 (from memory cache)。结论:这是最利用缓存的方式,也是用户最常见的行为。
当你按下 F5 时,浏览器的潜台词是:"我不信本地缓存是新的,我要去服务器问问。"
这时候,浏览器会无视强缓存(Cache-Control)的有效期。哪怕 max-age 还有 100 年,它也会强制发起 HTTP 请求。 但是!它还没彻底放弃治疗,它会带上 If-Modified-Since 或 If-None-Match 去问服务器:"这文件改了吗?"
结论:F5 会跳过强缓存判断,直接进行协商缓存。
这是开发者的最爱:强制刷新。
浏览器的潜台词是:"把旧的都扔了,给我来份全新的!"
这时候,浏览器会做两件事:
Cache-Control: no-cache 和 Pragma: no-cache。这相当于告诉服务器:"别给我 304,我不要旧的,给我 200 和最新内容。"
结论:完全绕过所有缓存机制,就像第一次访问一样。
细心的同学在 Chrome Network 面板里会发现,同样是缓存,有时候显示 from memory cache,有时候显示 from disk cache。这又有啥区别?
面试官如果追问这个,多半是想考察你对浏览器进程模型的理解。
冷知识:浏览器通常会把小文件、频繁使用的文件扔内存;大文件、不常用的扔硬盘。但这完全由浏览器内核控制,开发者干预不了。
作为开发者,我们的终极目标是:既要缓存时间够长(省流量),又要更新够快(不发版事故)。
既然 F5 和回车的行为不可控,我们就得从文件名下手。
目前前端工程化(Webpack/Vite)的标准解法是:Content Hash。
app.a1b2c3d4.js。index.html 设置 Cache-Control: no-cache,每次都去服务器拿最新的 HTML。app.a1b2c3d4.js 设置 Cache-Control: max-age=31536000(一年)。效果:
index.html 引用了新的 app.e5f6g7h8.js。浏览器一看:"哟,新文件,没缓存过",立刻请求新的。这样,用户根本不需要关心是回车还是 F5,永远能看到最新代码,同时享受最强缓存。
简洁版(30 秒):
浏览器对缓存的处理会根据用户行为降级。 正常访问(回车/链接)最优先使用强缓存,有效就不发请求。 F5 刷新会跳过强缓存,强制发起请求进行协商缓存(检查 ETag/Last-Modified)。 Ctrl+F5 强制刷新则是完全绕过所有缓存,请求头带 no-cache,直接向服务器要最新资源。
进阶版(1 分钟,带原理):
这本质上是浏览器在请求头里加了不同的指令。 正常访问时,浏览器查找 Disk/Memory Cache,命中强缓存则拦截请求。 当用户按 F5,浏览器会在请求头加
Cache-Control: max-age=0,这就导致强缓存失效,迫使服务器进行协商缓存验证(304 判断)。 而 Ctrl+F5 更暴力,它会加Cache-Control: no-cache和Pragma: no-cache,不仅不读本地缓存,还暗示中间代理服务器(如 CDN)也别给旧货,必须回源拿最新的。 所以在实际项目中,我们不能依赖用户的刷新行为,而是应该用 HTML 协商缓存 + 静态资源 Hash 文件名 + 强缓存 的组合拳,来保证更新和性能的平衡。