浏览器缓存(Browser Cache)是指浏览器会将访问过的资源(如 HTML、CSS、JS、图片等)存储在本地,以便在后续请求时可以直接从缓存中获取,而不是重新向服务器请求。在 Web 开发中,浏览器缓存是一项至关重要的优化技术,它能够减少网络请求,提升页面加载速度,提高用户体验,同时降低服务器负载。
缓存机制主要通过 HTTP 头部控制,包括强缓存(Strong Cache)和协商缓存(Negotiated Cache)。强缓存会直接读取本地缓存,协商缓存则是与服务器协商读取。缓存未过期使用强缓存机制,过期了则使用协商缓存机制。
%%{ init: { 'theme': 'base', 'themeVariables': { 'primaryBorderColor': '#7C0000', 'lineColor': '#fff', 'tertiaryColor': '#fff', 'fontSize': '12px', 'max-width': '10000px' } } }%% graph TD; A[浏览器请求资源] --> B{是否存在缓存} B -- 否
no-store --> C[向服务器请求资源] C --> D{决策返回
200 or 304?} D -- 200 --> E[设置缓存信息
Last-Modified, ETag] E --> F[使用资源] D -- 304 --> G[更新缓存信息
Last-Modified, ETag] G --> F B -- 是 --> H{是否可直接使用} H -- 否
no-cache --> I[获取验证信息
If-Modified-Since / If-None-Match] I --> C H -- 是 --> K{是否过期
expires/max-age/s-maxage} K -- 是 --> I K -- 否 --> L[从缓存中获取资源] L --> F
(缓存控制流程图)
强缓存(Strong Cache)
强缓存指的是浏览器直接从本地缓存中读取资源,状态码返回 200
,无需向服务器发起请求。
强缓存利用 HTTP 头部中的 Expires
和 Cache-Control
控制。
- Expires
Expires
指定资源的过期时间,在此时间之前,浏览器直接从缓存读取资源,无需请求服务器。
1 | Expires: Fri, 22 Mar 2025 12:00:00 GMT |
Expires
在 HTTP/1.0 规范中定义,由于 Expires 是服务器设置的绝对时间,可能会因客户端与服务器时间不同步而失效,因此在 HTTP/1.1 中被 Cache-Control
取代。
- Cache-Control
Cache-Control
是 HTTP/1.1 中引入,提供了更精细的缓存控制。
1 | Cache-Control: max-age=3600, public |
Cache-Control
一些常设置值:
1 | * max-age=xxx 过期时间,表示资源可以缓存多少秒(3600 即 1 小时),基于相对时间 |
注意:no-cache
(必须验证) 和 max-age=0
(过期) 都表示直接使用协商缓存。
为了向后兼容性,一般会同时设置 expires
和 Cache-Control
,比如 Nginx 配置中:
1 | location /static/ { |
协商缓存(Negotiated Cache)
缓存过期或 Cache-Control
被设置为 max-age=0
或 no-cache
,这时会采用协商缓存机制与服务器验证缓存是否仍然有效。如果资源没有改变,服务器会返回 304 Not Modified
,浏览器继续使用本地缓存。
注:有缓存下,会优先使用强缓机制,缓存过期才使用协商缓存机制。
协商缓存利用 Last-Modified
,If-Modified-Since
和 ETag
、If-None-Match
这两组 HTTP 头部控制。
- Last-Modified 和 If-Modified-Since
1 | Last-Modified: Mon, 20 Mar 2025 10:00:00 GMT |
服务器返回资源时,会带上 Last-Modified
头部,表示资源的最后修改时间。浏览器在下次请求时,会在请求头中带上 If-Modified-Since
,其值是上一次请求时返回的 Last-Modified
值,询问服务器资源是否更新。
1 | If-Modified-Since: Mon, 20 Mar 2025 10:00:00 GMT |
服务器再次收到资源请求时,根据传过来 If-Modified-Since
和资源在服务器上的最后修改时间比较日期大小,判断资源是否有变化,如果资源没有变更,服务器返回 304 Not Modified
,浏览器继续使用缓存,否则,服务器返回新的资源。Last-Modified
头部会更新为新生成的返回。
注意:如果资源内容没有变,但 Last-Modified 发生变化,仍然会触发重新下载。
- ETag 和 If-None-Match
Last-Modified
只能精确到秒级,在高频操作中,如果资源在 1 秒内被多次修改,Last-Modified
无法区分这些变化,另外 Last-Modified
依赖服务器时间,如果服务器时间被修改(比如时钟回拨),可能会导致缓存错乱。
为了更精准地判断资源是否变化,可以使用 ETag
(Entity Tag)。ETag
更精确,不受时间精度影响,能识别内容相同但修改时间不同的情况。
1 | ETag: "abc123" |
ETag
是资源的唯一标识符(通常是文件的哈希值)。浏览器请求时,会带上 If-None-Match
头部,值是上一次请求时返回的 ETag
的值。
1 | If-None-Match: "abc123" |
服务器再次收到资源请求时,根据传过来 If-None-Match
和新生成的 ETag
比较,如果相同,返回 304 Not Modified
,否则返回新的资源。ETag
头部会更新为新生成的返回。
浏览器缓存策略的选择
对于 HTML 文件这样更新频率高的文件,采用 Cache-Control: no-cache
+ Last-Modified
+ ETag
协商缓存机制。
1 | Cache-Control: no-cache |
对于 JS、CSS、图片等更新不频繁的静态资源文件,采用 Cache-Control: max-age
强缓存机制。但应配合文件指纹或内容哈希使用,否则强缓存机制会导致缓存更新问题。
1 | Cache-Control: max-age=31536000, immutable |
现代前端开发中,文件名普遍使用内容哈希,这类资源,可放心采用强缓存机制。
CSR 项目:
1 | # 默认采用协商缓存 |
SSR 项目:
1 | location /_nuxt/ { |
- 缓存更新问题
在强缓存机制下,甚至没有配置 ETag
的协商缓存机制下,可能会出现更新资源无法生效问题,浏览器会继续加载旧资源。
使用以下方案解决避免:
1 | * 使用带 Cache-Control: no-cache + Last-Modified + ETag 的协商缓存机制,不走强缓存 |