浏览器缓存策略
想象一下浏览器就像一个有记忆的快递员,它负责从网站服务器(仓库)给你取东西(网页、图片、脚本等)。缓存策略就是告诉这个快递员:“哪些东西你可以记住(缓存),下次直接给我,不用再跑仓库了?什么时候需要再去仓库确认一下东西有没有变?”
核心目标:减少不必要的网络请求,加快网页加载速度,节省带宽。
缓存策略主要分为两大类:
- 强缓存:浏览器“自作主张”,完全不问服务器,直接用本地缓存。
- 协商缓存:浏览器会问服务器:“我本地这个缓存还能用吗?东西变没变?”服务器根据情况决定是让浏览器用缓存还是给新内容。
一、强缓存:快递员自己决定用不用存货
浏览器在发起请求前,会先检查本地缓存。如果缓存有效,浏览器直接使用缓存,完全不发送请求到服务器!速度最快。
判断缓存是否有效,主要看两个 HTTP 响应头
-
Expires(HTTP/1.0 产物)
- 作用:告诉浏览器这个资源的“保质期”到什么时候。
- 值:一个具体的日期时间(如 Wed, 21 Oct 2025 07:28:00 GMT)。
- 判断逻辑:浏览器拿当前时间和 Expires 时间对比。如果当前时间早于 Expires,缓存有效,否则无效
- 缺点:依赖客户端(浏览器)的本地时间。如果用户电脑时间不准,缓存可能提前失效或持续有效。
-
Cache-Control(HTTP/1.1 引入,优先级高于 Expires)
作用:更强大、更灵活地控制缓存行为。
常用指令:- max-age:最重要的指令,单位是秒。告诉浏览器该资源在多少秒内是新鲜的(新鲜的就可以直接用)。如Cache-Control: max-age=3600 表示 1 小时内有效 - public:响应可以被任何中间代理(如 CDN)和浏览器缓存。适合所有人共享的资源。- private:响应只能被用户浏览器缓存,不能被中间代理缓存。适合个性化内容 - no-cache:注意!不是“不缓存”,而是“缓存了但每次都要去服务器验证”。
no-store:真正的不缓存。浏览器和任何中间代理都不能存储这个响应的任何部分。每次都得去服务器拿。
immutable:(较新)表示资源永远不会变。在新鲜期内(由 max-age 指定),即使用户刷新页面,也不会去服务器验证。适合带哈希指纹的资源(如 main.abcd1234.js)。
must-revalidate:一旦缓存过期(超过 max-age),必须去服务器验证,不能直接用过期缓存。
判断逻辑:浏览器计算资源被缓存的时间 + max-age 秒,看是否超过当前时间。没超过,缓存有效,直接用。
注意:
- 浏览器开发者工具里看到的 200 (from disk cache) 或 200 (from memory cache),表示资源来自强缓存,没有向服务器发请求。
- 强缓存失效后,浏览器会带缓存标识去服务器验证(协商缓存)或直接发新请求(取决于资源类型和设置)。
二、协商缓存:快递员先去仓库确认一下
当强缓存失效(过期了)或者响应头明确要求协商(如 Cache-Control: no-cache),浏览器就会向服务器发起一个验证请求。这个请求会带上一些关于本地缓存的信息。服务器根据这些信息判断缓存是否还能用。
关键点:协商缓存一定会发起请求,但如果缓存有效,服务器只返回一个很小的响应头(通常是 304),告诉浏览器“缓存还能用”,浏览器就直接用本地缓存。这比下载整个资源快得多。
协商缓存主要依赖两组请求头/响应头:
- Last-Modified / If-Modified-Since
原理:基于文件的修改时间。
流程:- 服务器第一次响应资源时,响应头加 Last-Modified,值是资源最后修改时间。
- 浏览器缓存这个时间。
- 需要验证缓存时(强缓存失效或 no-cache),浏览器请求头带 If-Modified-Since,值为上次缓存的 Last-Modified。
- 服务器比较 If-Modified-Since 和资源实际修改时间:
- 没变(服务器时间 `<=` If-Modified-Since):返回 304,浏览器用本地缓存。
- 变了(服务器时间 > If-Modified-Since):返回 200 和新内容,浏览器更新缓存。
2. ETag / If-None-Match
优先级:
- 服务器通常会同时提供 ETag 和 Last-Modified。
- 浏览器验证时会同时带 If-None-Match 和 If-Modified-Since。
- 服务器优先检查 If-None-Match(ETag),只有 ETag 不可用时才用 Last-Modified。
三、缓存位置:快递员把东西放哪儿了?
浏览器缓存有不同层级,访问速度和生命周期不同
-
Service Worker Cache
- 最高级控制:需开发者注册 Service Worker 并编写缓存逻辑。
- 功能强大:可实现“缓存优先”、“网络优先”等策略,支持离线应用。
- 持久性:除非明确删除,否则长期存在。
-
Memory Cache(内存缓存)
- 速度最快,直接从内存读取。
- 容量小,生命周期短(关闭标签页或浏览器就没了)。
- 常用于当前会话中频繁访问的资源。
-
Disk Cache(硬盘缓存)
- 速度较慢,需要磁盘 I/O。
- 容量大,生命周期长(关闭浏览器后依然存在)。
- 是缓存的主力军。
-
Push Cache(推送缓存)
- HTTP/2 特性,服务器主动推送的资源暂存在这里。
- 生命周期极短,只在当前会话有效。
- 实际应用较少。
访问顺序:浏览器查找缓存时,通常按 Service Worker → Memory Cache → Disk Cache → Push Cache 的顺序查找。找到即用,找不到才发网络请求。
四、如何设计好的缓存策略?
没有一刀切的策略,需要根据资源类型决定:
-
频繁变动(如 HTML 文件,API 数据)
- Cache-Control: no-cache 或 Cache-Control: no-store(如果非常敏感)。
- 配合 ETag 或 Last-Modified 进行协商缓存。每次都验证,但能用 304 节省带宽。
-
不常变动(如 JS/CSS 库、字体、图片)
- 最佳实践:使用内容哈希,文件名中带哈希值(如 main.abcd1234.js)。内容变,文件名变。
- 设置超长强缓存:Cache-Control: public, max-age=31536000(一年)。
- 可加 immutable,告诉浏览器新鲜期内连刷新都不用验证。
-
介于两者之间(如用户头像可能会更新的图)
- 设置中等时长强缓存:Cache-Control: public, max-age=86400(一天)。
- 配合 ETag 或 Last-Modified。有效期内直接用,过期后去服务器验证。
总结关键点
- 强缓存(Expires, Cache-Control):浏览器自己判断,有效就不发请求(200 from cache)。
- 协商缓存(Last-Modified/If-Modified-Since, ETag/If-None-Match):浏览器发小请求问服务器,缓存有效返回 304,无效返回 200 和新内容。ETag 比 Last-Modified 可靠
- 缓存位置:分多层(Service Worker, Memory, Disk, Push),速度、容量、生命周期不同。
- 策略设计:根据资源类型选择。内容哈希 + 长缓存是静态资源的最佳实践。
常见易混淆点小结
no-cache 并不是“不缓存”,而是“缓存了但每次都要去服务器验证”。
no-store 才是完全不缓存。
ETag 比 Last-Modified 更精确,但两者可配合使用。
Service Worker Cache 需开发者主动实现。
- 强制刷新会跳过所有缓存,服务器一定返回新资源。
理解并合理运用浏览器缓存策略,是优化网站性能、提升用户体验的关键一步!