浏览器缓存的核心思想
核心目的是减少网络请求的发起次数 和减少服务器需要传输的数据量,从而显著提升页面加载速度,减轻服务器压力,提升用户体验。
浏览器的缓存策略主要分为两个阶段:强缓存 和协商缓存。
分层解析
强缓存 (Strong Cache)
核心: 不问服务器,直接用自己的副本。
目标: 根本不会发起HTTP请求。
-
工作原理 :浏览器在请求一个资源时,会先检查本地缓存。如果发现该资源的缓存副本,并且它尚未过期 ,浏览器就会直接使用这个副本,完全不会向服务器发送任何请求 。这个过程发生在浏览器内部,是静默的,因此在开发者工具的Network面板中看到该请求的状态码是
200 (from disk cache)表示在磁盘当中打开多次的资源
或200 (from memory cache)表示在内存当中,浏览器关闭则释放
。 -
如何控制 - HTTP Header:
-
Expires
(HTTP/1.0):一个绝对的过期时间(GMT格式),例如Expires: Wed, 21 Oct 2024 07:28:00 GMT
。缺点是如果客户端和服务器时间不一致,会导致缓存判断错误。 -
Cache-Control
(HTTP/1.1):优先级高于Expires
,提供了更灵活、更精确的控制。常用指令:max-age=
:设置缓存的最大有效时间,单位是秒(相对时间)。例如Cache-Control: max-age=3600
表示资源1小时内有效。public
:响应可以被任何对象(客户端、代理服务器等)缓存。private
:响应只能被单个用户(浏览器)缓存,不能被代理服务器缓存。no-cache
:不是不缓存 ,而是使用缓存前,必须先向服务器验证(即跳过强缓存,直接进入协商缓存阶段)。no-store
:真正的不缓存,完全不使用任何缓存策略。每次都要从服务器重新获取。
-
协商缓存 (Negotiation Cache)
核心: 问问服务器,我这个副本还能不能用。
目标: 可能省去传输响应体的开销。
-
触发条件 :当强缓存失效(过期)时,浏览器就会启用协商缓存。
-
工作原理 :浏览器会向服务器发起一个条件性请求,携带一些"验证字段"。服务器根据这些字段判断客户端的缓存副本是否依然有效。
- 如果有效,服务器返回
304 Not Modified
,响应体为空,告诉浏览器:"你的副本没变,继续用吧!"。 - 如果失效,服务器返回
200 OK
和完整的资源内容。
- 如果有效,服务器返回
-
如何控制 - 成对的HTTP Header:
-
第一对:
Last-Modified
/If-Modified-Since
- 服务器响应 :
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
(资源最后修改时间)。 - 浏览器请求 :下次请求时,带上
If-Modified-Since: [上次收到的Last-Modified值]
。 - 缺点:精度到秒,如果文件在1秒内多次改动,无法识别;有时候文件内容没变,但修改时间变了(比如touch了一下),会导致不必要的重新下载。
- 服务器响应 :
-
第二对:
ETag
/If-None-Match
(优先级更高)- 服务器响应 :
ETag: "a5d7f8e3q1w2"
(一个唯一标识符,通常是文件的哈希值或版本号)。 - 浏览器请求 :下次请求时,带上
If-None-Match: [上次收到的ETag值]
。 - 优点 :比
Last-Modified
更精确,能感知文件内容的微小变化。完美解决上述"秒级修改"和"仅修改时间"的问题。
- 服务器响应 :
-
对比和总结
特性 | 强缓存 | 协商缓存 |
---|---|---|
是否发请求 | 不发 | 发 |
目标 | 完全不请求,极致速度 | 可能省去响应体(返回304) |
状态码 | 200 (from cache) | 304 Not Modified |
控制字段 | Cache-Control , Expires |
ETag /If-None-Match , Last-Modified /If-Modified-Since |
优先级 | 先执行 | 强缓存失效后执行 |
完整的缓存决策流程可以概括为:
浏览器加载资源时 -> 先看有没有缓存 -> 有缓存,再看Cache-Control
/Expires
是否过期 -> 未过期 ,强缓存生效 -> 已过期 ,则发起请求带上If-None-Match
/If-Modified-Since
-> 服务器验证 -> 有效返回304,无效返回200和新资源。
实际应用中
在实际的前端项目中,我们通常会通过Webpack、Vite等构建工具来管理静态资源的缓存:
- 哈希文件名 :我们对静态资源(JS, CSS, 图片)的文件名使用内容哈希(如
app.a5d7f8e3.js
)。这样一旦文件内容改变,文件名就会变,相当于请求了一个全新的URL,因此可以设置非常长的强缓存时间(比如一年) ,即Cache-Control: max-age=31536000
。这是性能最佳实践。 - HTML文件 :HTML是入口文件,通常我们将其设置为
Cache-Control: no-cache
或较短的最大存活时间,确保用户能及时获取到最新的页面和最新的资源链接。
通过这种策略,我们既保证了静态资源的高缓存命中率,又能保证内容的及时更新。
如果使用了强缓存出现了BUG(项目更新之后,用户浏览器还在用旧文件,导致页面显示不正常)
-
资源文件名(指纹化)
这是最彻底、最推荐的做法。原理是:如果文件内容变了,那么文件的名字也变,对于浏览器来说,这就是一个全新的资源,自然会发起新的请求。 实现方法是在文件名中嵌入一个"指纹"(Hash),这个指纹根据文件内容计算得出。内容一变,指纹必变。
- 原始文件:
styles.css
- 指纹化后:
styles.a1b2c3d4.css
(哈希值随内容变化)
如何实现指纹化?
你通常不需要手动做这件事,现代前端构建工具(Webpack, Vite, Rollup, Parcel 等)可以自动完成。
-
Webpack: 使用
[contenthash]
占位符。javascript
css// webpack.config.js output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].chunk.js', }
-
Vite: 生产环境构建默认就会为静态资源添加哈希后缀。
配套的 HTML 处理:
构建工具在生成带哈希的文件名后,也会自动更新 HTML 中引用的路径,从
<link href="styles.css">
变成<link href="styles.a1b2c3d4.css">
。服务器配置:
为你所有的静态资源(JS, CSS, 图片,字体等)设置长期强缓存。
bash# Nginx 配置示例 location /static/ { alias /path/to/your/static/files/; # 设置一年强缓存 expires 1y; add_header Cache-Control "public, immutable"; }
注意看,我们缓存的是
/static/
目录下的文件,这些文件都是被指纹化的。非指纹化的文件(如index.html
)绝对不能设置强缓存。总结与最佳实践流程
方案 策略 适用场景 优点 缺点 文件名指纹化 根本方案 所有新项目和生产环境 一劳永逸,完美平衡性能与更新 需要构建工具支持 CDN 刷新 紧急预案 线上出现紧急 bug 生效快,针对性强 是补救措施,非预防手段 查询字符串 临时方案 快速测试或紧急热修复 简单,无需工具链 缓存可靠性有争议 - 原始文件: