HTTP 缓存
- HTTP 缓存
- Cache-Control
- 缓存位置
- 协商缓存
- 缓存设置控制
- 前端 HTTP 缓存最佳实践
- SWR(stale-while-revalidate):是一种缓存策略:优先使用缓存,然后再更新缓存
Cache-Control
http 中控制缓存的主要字段有一下三个:
- Cache-Control(HTTP/1.1,优先级高)
- Expires(HTTP/1.0)
HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间)
- Pragma: no-cache(相当于 Cache-Control: no-cache,主要是为了兼容 HTTP/1.0)
重点学习
Cache-Control
- Cache-Control
- no-store,不允许存储缓存资源
- no-cache,不允许先使用缓存资源,强制发送请求(协商缓存验证)
- max-age,缓存时间,相对响应报文的创建时刻
当没有显示设置 cache-control 或是 expires 时, 大部分浏览器会使用启发式缓存, 把资源缓存下来; 如果真的不想用缓存, 还是主动设置一下 cache-control: no-store。
启发式计算缓存在 RFC 里的建议是 (Date - Last-modified) * 10% - must-revalidate,当缓存失效时必须与回源服务器验证
当缓存失效时,其实带不带 must-revalidate,都会发送请求,那么 must-revalidate 好像没什么作用?主要有一下两个使用场景:
- HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如服务器关闭或失去连接,导致请求发送失败的时候,即使设置了
Cache-Control: max-age=0还是回继续使用缓存;还有比如有配置一些特殊指令(stale-while-revalidate、stale-if-error 等)的时候也会导致继续使用缓存,可以使用 must-revalidate 进行阻止。 - 与 proxy-revalidate(下文介绍)做区别,must-revalidate 强调回源服务器
- HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如服务器关闭或失去连接,导致请求发送失败的时候,即使设置了
代理缓存

缓存代理身份特殊,即是客户端也是服务端,所以还需要有一些新的“Cache-Control”属性来对它做细致的控制。
- private,表示缓存只能在客户端保存,不能放在代理上与别人共享
- public,缓存完全开放,谁都可以存,谁都可以用
- proxy-revalidate,缓存失效时代理服务器验证即可
- s-maxage,单独设置代理服务器缓存时间,与 max-age 区别开
- no-transform,禁止代理服务对资源做转换
浏览器缓存
浏览器对资源的缓存位置分为:
- 内存
- service-work
- http-cache
- push-cache
⚠️ 内存缓存的行为,各个浏览器并没有统一规范,而且内存缓存并不关注 HTTP 语义,浏览器导航中会重用资源,即是资源带有
max-age=0或no-cache。
唯一可能例外的是no-store内存缓存在某些情况下确实会遵守该指令。
HTTP Cache 几乎遵从 HTTP 规范,但有一个例外,即是资源带有,HTML 中 prefetch 指令获取的资源会缓存在 HTTP Cache 中一定时间(5分钟)
浏览器 http-cache 策略分为两种:强缓存、协商缓存

- 缓存查找:查找不到直接发送请求
- 强制缓存:通过 Expires 和 Cache-Control 判断缓存是否有效,如果可用则直接使用,否则协商缓存
- 协商缓存:服务端返回资源时会带有 Last-modified、ETag信息,当协商缓存时,客户端直接发起请求并且携带 If-Modified-Since 、If-None-Match 去请求后台,服务器根据条件请求字段判断资源是否更新
- 若资源更新,返回资源和 200 状态码
- 否则,返回 304,告诉浏览器直接从缓存获取资源
用户行为
以下 chrome 实现效果
💡 浏览器某些用户行为会在请求头带上“私货”以控制缓存:
- 地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制
- 前进后退也会触发浏览器缓存机制,但某些情况浏览器会直接缓存内存到时直接读取即可(Back/forward cache )
- 刷新行为会对 html 文件自动请求带上
Cache-Control:max-age=0 - 强刷或者禁止缓存所有请求会带上
Cache-Control: no-cache,并且不携带 If 条件验证,强制请求,不做协商。
协商缓存
HTTP 协议就定义了一系列“If”开头的“条件请求”字段,专门用来与服务器检查验证资源是否过期。当请求带有条件字段,服务器就会验证资源是否过期。
- If-Modified-Since 、Last-modified,根据文件修改日期做验证
- If-None-Match 、ETag,
ETag 是“实体标签”(Entity Tag)的缩写,是资源的一个唯一标识,文件内容的 hash 值。比 Last-modified 做判断更精准,做验证时优先级比 Last-modified 高, 因为
- Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么他们的 Last-Modified 其实并没有体现出来修改
- 有时一个文件内容没什么变化,但修改时间发生了变化。
ETag 还有“强”“弱”之分。强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。
ETag 工作原理:

Last-modified 也同样类似。
缓存控制
通过 response 进行缓存控制

通过 request 进行缓存控制
Cache-Control 是个通用字段,客户端也可以发送附带 Cache-Control 缓存指令的请求(但浏览器对请求缓存控制的支持有限,比如仅支持 Cache-Control: max-age=0 或者 Cache-Control: no-cache 去做刷新)。

- no-cache,不使用缓存,直接发送请求
- max-stale,表明客户端愿意接收一个超过指定过期时间范围内的资源,比如
max-stale: 5过期后 5 秒内都可以 - min-fresh,相当于缩短过期时间,比如
min-fresh:5,只允许到期前 5 秒之前的时间内 - only-if-cached,表示只接受代理缓存的数据,不接受源服务器的响应
- no-transform,禁止代理服务对资源做转换
Vary:缓存验证器
URL 原则上是一种网络上的资源概念,同个 URL 可以有多种资源版本形式。

比如,你可以 Accept: text/html,也可以 Accept: text/csv 改为以不同的格式获取相同的资源,这些都是服务器内容协商的结果。
vary 虽然不是 cache-control 的属性值,是内容协商的结果,带在响应头部,表示一个内容版本,可协作缓存决策依据。
大多数浏览器都支持 vary 缓存验证,但要注意的是浏览器通常不会实现为同个 URL 存储多个变体的功能,只会为唯一 URL 做单一内容版本存储;而代理服务器通常可自定义实现对同个 URL 多个 vary 缓存。
下图是代理缓存根据 vary 缓存依据流程:

前端 HTTP 缓存最佳实践
原则:依据是否可 URL 版本化控制,分为永不缓存(协商),或永远缓存
- 可做 URL 版本化控制的文件:长期缓存
Cache-Control: public,max-age=31536000,immutable,如 webpack 模块化打包出来 js、css 等这类能够通过工具自动化打包链接的文件切勿打包过大变动时导致缓存失效,需代码分割,进行细粒度控制缓存 代码分割划分:业务代码、公共代码、第三方库
- 不可 URL 版本化控制的文件:协商缓存
Cache-Control: no-cache与etag/last-modified,如 index.html 等