「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
相信学过 http 的小伙伴都或多或少了解过状态码,其中的 304 Not Modified 意思是请求资源未发生改变,那么在什么时候服务器会返回该状态码呢?这就要从浏览器的缓存策略讲起。
浏览器缓存策略
浏览器缓存策略可以分为两种,强缓存和协商缓存(注意这里的用词,两者都是调用缓存的策略,并不是本地缓存的方式!这是我一开始学的时候的误区)。
浏览器缓存的大致调用过程:
- 浏览器加载资源时,首先会通过 Expires 或 Cache Control 关键字来判断是否命中强缓存,命中则直接从本地缓存读取资源,未命中则向服务器发起请求。
- 当浏览器向服务器发送请求时,会携带 If-Modified-Since 或 If-None-Match 关键字,来判断是否命中协商缓存,命中则调用本地,没有命中服务器会返回最新数据
可以看到强缓存优先级高于协商缓存。或许你会对上述过程有些疑问,我会在下面具体讲解详细过程。
什么是强缓存?
强缓存就是资源直接从本地获取,不需要发起请求(协商缓存通过名字其实也可以判断出来需要发起请求)。
强缓存是通过响应头里的 Expires 或 Cache Control 来实现的。
当我们初次向服务器发起请求,服务器会返回相应的资源,那么在响应头里会带上一个 Expires 或 Cache Control。前者是 http/1.0 中使用的,后者是 http/1.1 中使用的,当两者同时出现时,后者会覆盖前者。
Expires
Expires 即过期时间,它返回的是一个标准时间,在这个时间之前,浏览器再次获取这个资源不需要发起请求,直接从本地缓存中获取即可。
Expires: Wed, 22 Nov 2019 08:41:00 GMT
既然 Expires 可以解决问题,那为什么 http/1.1 还要再出一个 Cache Control 呢?其实主要是因为 Expires 返回的是一个标准时间,但有些电脑它的时间可能与服务器时间不同,那么这里返回的绝对时间就会导致过期的时间并不准确,这也是为什么这种方式被 http/1.1 抛弃了,转而使用相对时间。
Cache Control
| 指令 | 参数 | 说明 |
|---|---|---|
| no-cache | 无 | 缓存前必须确认本地缓存的有效性 |
| no-store | 无 | 不缓存请求或响应的任何内容,注意跟 no-cache 区分 |
| max-age = [秒] | 必须 | 调用缓存的最大时间 |
| public | 无 | 资源公有,任意方都可以缓存 |
| private | 可省略 | 资源私有,缓存只能被特定的客户端缓存 |
| ... | ... | ... |
Cache Control 中有非常多的指令,通过它我们就可以控制缓存的行为,在这里主要讲 max-age 这一指令,长下面这样。
max-age 返回的就是相对时间,代表接收到这个响应的 1800 秒内,再次访问该资源无需发起请求。
什么是协商缓存?
当强缓存无法使用时,就要用到协商缓存,协商缓存需要向服务器发起请求,来判断是否可以使用本地缓存。不知道你在看强缓存时,有没有这么一丝困惑,虽说强缓存在一定的时间内可以直接使用本地缓存,可万一服务器端的资源发生了变动,那么此时再去使用本地的过期资源就不再合适,协商缓存就是为解决这一问题而生。
协商缓存也同样有两种方式可以去实现,一种是 Last Modified / If-Modified-Since ,另一种是 Etag / If-None-Match,要么使用第一种要么使用第二种。
Last Modified / If-Modified-Since
当浏览器初次向服务器发起请求时,服务器会返回相应的资源并在响应头中带着 Last Modified,即资源最后修改时间,返回的也是标准时间。
以后每次请求该资源,浏览器都会在请求头里写上 If-Modified-Since,而它的值就是 Last Modified 的值,这样写的意思解释一下就是说,浏览器想用本地缓存,但是浏览器不知道服务器上的资源有没有发生变动,所以为了方便知道是否更新资源,服务器会在返回资源时告诉浏览器该资源最后改变的时间,而浏览器想要知道资源有没有发生变动,只需要在请求中进行询问,比如这里就是从返回的某某时间后有没有发生改变啊?若是没有发生改变,那么服务器会返回 304 Not Modified,告诉浏览器,资源没有发生变化,于是浏览器就可以安心地使用本地缓存了。如果发生变化了,那么服务器会返回 200 OK,并返回最新的资源,同时也会返回最新的 Last Modified。
Etag / If-None-Match
Etag 的使用跟上面的使用是完全相同的,Etag 是资源的唯一标识,当资源发生变化时,这个值就会随之发生变化,If-None-Match 意思就是如果没有匹配的 Etag,Etag 和 If-None-Match 也都是通过头部进行传输的,具体过程如下,当浏览器初次发起请求时,服务器返回相应资源并在响应头中带上 Etag 用来唯一标识该资源文件,以后每一次使用该资源时,浏览器都会先向服务器发起请求,请求头中会携带 If-None-Match,它的值就是 Etag 的值,服务器收到请求后会进行相应查询,若没有与之匹配的值,就说明资源文件已经发生变动,那么此时会返回 200 并带上最新的资源,同时更新 Etag,若有与之相匹配的值,那么说明资源没有发生变化,此时会返回 304,不返回任何资源文件,浏览器就知道此时应该去调用本地缓存了。
关于 Last Modified 和 Etag
当 Response Header 中二者同时出现时,会优先使用 Etag 而不是 Last Modified,因为 Etag 更加精确,而 Last Modified 只能精确到秒。Etag 的缺点在于计算会有性能消耗。
缓存位置
浏览器中的缓存一共有四种位置,优先级从高到低依次为:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
强缓存和协商缓存应用场景
这个具体可以看下面的参考文章,简单总结下 对于经常需要发生变动的文件需要去使用协商缓存或者不用缓存比如 html 文件,而对于不需要经常变动的文件比如引入的一些第三方库的 js、css 文件就可以使用强缓存