缓存是前端性能优化中很重要的一个点,对缓存的理解也是衡量对http请求响应头是否理解。
强缓存
、协商缓存
、启发式缓存
http header中与缓存有关的key
缓存协商策略用于重新验证缓存资源是否有效
Cache-Control
浏览器缓存里, Cache-Control是金字塔顶尖的规则, 它藐视一切其他设置, 只要其他设置与其抵触, 一律覆盖之
不仅如此, 它还是一个复合规则, 包含多种值, 横跨 存储策略, 过期策略 两种, 同时在请求头和响应头都可设置.
语法为: “Cache-Control : cache-directive”.
Cache-directive共有如下12种(其中请求中指令7种, 响应中指令9种):
假设所请求资源于5月5日缓存, 且在5月12日过期.
当max-age 与 max-stale 和 min-fresh 同时使用时, 它们的设置相互之间独立生效, 最为保守的缓存策略总是有效. 这意味着, 如果max-age=10 days, max-stale=2 days, min-fresh=3 days, 那么:
-
根据max-age的设置, 覆盖原缓存周期, 缓存资源将在5月15日失效(5+10=15);
-
根据max-stale的设置, 缓存过期后两天依然有效, 此时响应将返回110(Response is stale)状态码, 缓存资源将在5月14日失效(12+2=14);
-
根据min-fresh的设置, 至少要留有3天的新鲜期, 缓存资源将在5月9日失效(12-3=9);
由于客户端总是采用最保守的缓存策略, 因此, 5月9日后, 对于该资源的请求将重新向服务器发起验证.
Pragma
http1.0字段, 通常设置为Pragma:no-cache
, 作用同Cache-Control:no-cache
. 当一个no-cache请求发送给一个不遵循HTTP/1.1的服务器时, 客户端应该包含pragma指令. 为此, 勾选☑️ 上disable cache时, 浏览器自动带上了pragma字段. 如下:
Expires
Expires:Wed, 05 Apr 2015 00:55:35 GMT
即到期时间, 以服务器时间为参考系, 其优先级比 Cache-Control:max-age 低, 两者同时出现在响应头时, Expires将被后者覆盖. 如果Expires, Cache-Control: max-age, 或 Cache-Control:s-maxage 都没有在响应头中出现, 并且也没有其它缓存的设置, 那么浏览器默认会采用一个启发式的算法, 通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间
ETag
ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"
实体标签, 服务器资源的唯一标识符, 浏览器可以根据ETag值缓存数据, 节省带宽. 如果资源已经改变, etag可以帮助防止同步更新资源的相互覆盖. ETag 优先级比 Last-Modified 高.
If-Match
缓存校验字段, 其值为上次收到的
一个或多个etag
值. 常用于判断条件是否满足, 如下两种场景:
-
对于 GET 或 HEAD 请求, 结合 Range 头字段, 它可以保证新范围的请求和前一个来自相同的源, 如果不匹配, 服务器将返回一个416(Range Not Satisfiable)状态码的响应.
-
对于 PUT 或者其他不安全的请求, If-Match 可用于阻止错误的更新操作, 如果不匹配, 服务器将返回一个412(Precondition Failed)状态码的响应.
If-None-Match
缓存校验字段, 结合ETag字段, 常用于判断缓存资源是否有效, 优先级比
If-Modified-Since
高.
-
对于 GET 或 HEAD 请求, 如果其etags列表均不匹配, 服务器将返回
200
状态码的响应, 反之, 将返回304(Not Modified)
状态码的响应. 无论是200还是304响应, 都至少返回 Cache-Control, Content-Location, Date, ETag, Expires, and Vary 中之一的字段. -
对于其他更新服务器资源的请求, 如果其etags列表匹配, 服务器将执行更新, 反之, 将返回412(Precondition Failed)状态码的响应.
Last-Modified
Last-Modified: Tue, 04 Apr 2015 10:01:15 GMT
- 用于标记请求资源的最后一次修改时间, 格式为GMT(格林尼治标准时间). 如可用 new Date().toGMTString()获取当前GMT时间. Last-Modified 是 ETag 的fallback机制, 优先级比 ETag 低, 且只能精确到秒, 因此不太适合短时间内频繁改动的资源. 不仅如此, 服务器端的静态资源, 通常需要编译打包, 可能出现资源内容没有改变, 而Last-Modified却改变的情况.
If-Modified-Since
If-Modified-Since: Tue, 04 Apr 2015 10:12:27 GMT
- 缓存校验字段, 其值为上次响应头的Last-Modified值, 若与请求资源当前的Last-Modified值相同, 那么将返回304状态码的响应, 反之, 将返回200状态码响应.
If-Unmodified-Since
缓存校验字段, 语法同上. 表示资源未修改则正常执行更新, 否则返回412(Precondition Failed)状态码的响应. 常用于如下两种场景:
-
不安全的请求, 比如说使用post请求更新wiki文档, 文档未修改时才执行更新.
-
与 If-Range 字段同时使用时, 可以用来保证新的片段请求来自一个未修改的文档.
强缓存
一旦资源命中强缓存, 浏览器便不会向服务器发送请求, 而是直接读取缓存. Chrome下的现象是
协商缓存
缓存过期后, 继续请求该资源, 对于现代浏览器, 拥有如下两种做法:
-
根据上次响应中的ETag_value, 自动往request header中添加If-None-Match字段. 服务器收到请求后, 拿If-None-Match字段的值与资源的ETag值进行比较, 若相同, 则命中协商缓存, 返回304响应.
-
根据上次响应中的Last-Modified_value, 自动往request header中添加If-Modified-Since字段. 服务器收到请求后, 拿If-Modified-Since字段的值与资源的Last-Modified值进行比较, 若相同, 则命中协商缓存, 返回304响应.
以上, ETag优先级比Last-Modified高, 同时存在时, 前者覆盖后者. 下面通过实例来理解下强缓存和协商缓存.
如下忽略首次访问, 第二次通过 If-Modified-Since
命中了304协商缓存.
协商缓存的响应结果, 不仅验证了资源的有效性, 同时还更新了浏览器缓存
Age
出现此字段, 表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间, 单位为秒. 如下:
Age:2383321
Date:Wed, 08 Mar 2015 16:12:42 GMT
以上指的是, 代理服务器在2015年3月8日16:12:42时向源服务器发起了对该资源的请求, 目前已缓存了该资源2383321秒.
Date
指的是响应生成的时间. 请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久.
Vary
对于服务器而言, 资源文件可能不止一个版本, 比如说压缩和未压缩, 针对不同的客户端, 通常需要返回不同的资源版本. 比如说老式的浏览器可能不支持解压缩, 这个时候, 就需要返回一个未压缩的版本; 对于新的浏览器, 支持压缩, 返回一个压缩的版本, 有利于节省带宽, 提升体验. 那么怎么区分这个版本呢, 这个时候就需要Vary了.
服务器通过指定Vary: Accept-Encoding
, 告知代理服务器, 对于这个资源, 需要缓存两个版本: 压缩和未压缩. 这样老式浏览器和新的浏览器, 通过代理, 就分别拿到了未压缩和压缩版本的资源, 避免了都拿同一个资源的尴尬.
Vary:Accept-Encoding,User-Agent
如上设置, 代理服务器将针对是否压缩和浏览器类型两个维度去缓存资源. 如此一来, 同一个url, 就能针对PC和Mobile返回不同的缓存内容.
怎么让浏览器不缓存静态资源
实际上, 工作中很多场景都需要避免浏览器缓存, 除了浏览器隐私模式, 请求时想要禁用缓存, 还可以设置请求头: Cache-Control: no-cache, no-store, must-revalidate
.
当然, 还有一种常用做法: 即给请求的资源增加一个版本号, 如下:
<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>
这样做的好处就是你可以自由控制什么时候加载最新的资源.
不仅如此, HTML也可以禁用缓存, 即在页面的\节点中加入\标签, 代码如下:
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>
上述虽能禁用缓存, 但只有部分浏览器支持, 而且由于代理不解析HTML文档, 故代理服务器也不支持这种方式.