以前学习浏览器缓存的机制,都是通过记忆http报文字段,后来发现这样记忆方法很笨,今天重新梳理了下流程。
为何要缓存
首先,我们知道缓存是为了节省流量、加快网页加载速度、减少服务器压力的一种机制。就是把使用过的一些资源文件存在本地,方便再次使用。
如何缓存
- 客户端第一次访问服务器,加载并缓存了一部分资源
- 客户端第二次访问服务器,发现有缓存,查看缓存是否过期,进入强缓存阶段
强缓存
这一阶段主要是看两个字段:
Cache-Control
主要是看max-age(单位-秒)是否过期,这个是简单的时间对比,把第一次缓存的时间跟当前时间对比,这里有两种情况:
- 如果还在有效期,就直接使用缓存,不做其他判定了
- 如果过期了,就需要重新去判定缓存是否可用,原因是:时效期过了并不代表文件有改动(max-age值为100,但其实文件一直都没改动),会进入协商缓存阶段。
Expires
Expires是HTTP/1.0控制网页缓存的字段,在1.1版本中被Cache-Control替代了,二者同时存在,取Cache-Control。
Expires也是做失效对比的字段:Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,但他有明显的缺点,如果服务器和客户端不在同一时区(例如美国和中国),那么对比就没有意义了,所以后来废除了。他的时效期对比也是和Cache-Control同样的逻辑。
协商缓存
缓存文件时效期过了,进入协商缓存阶段。协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
这一阶段也是看两个字段:
Last-Modified / If-Modified-Since
这个字段主要是用于判定文件修改的时间。
我们第一次缓存文件的时候,会储存响应头中的Last-Modified字段,在缓存时效期过了之后,会带上If-Modified-Since(值为缓存下来的Last-Modified)字段去请求服务器的文件:
服务器发现请求头中带有If-Modified-Since,那么就会取对比文件的Last-Modified:
- Last-Modified / If-Modified-Since相等,代表文件没有改动,返回304状态码,客户端收到后就继续使用缓存文件
- 如果值不一致,则文件代表有改动,服务器就会返回改动后的资源文件,客户端会重新加载文件,然后缓存下来
Etag / If-None-Match
跟前一个字段一样,只是优先级高于Last-Modified(同时存在取Etag)。
这个字段主要是用于判断文件版本,相当于给当前文件打上一个标记,当文件修改之后,标记也会变,没有改动则不变。
我们第一次缓存文件的时候,会储存响应头中的Etag字段,在缓存时效期过了之后,会在请求头带上If-None-Match(值为缓存下来的Etag)字段去请求服务器的文件:
服务器发现请求头中带有If-None-Match,那么就会取对比文件的Etag:
- Etag / If-None-Match相等,代表文件没有改动,返回304状态码,客户端收到后就继续使用缓存文件
- 如果值不一致,则文件代表有改动,服务器就会返回改动后的资源文件,客户端会重新加载文件,然后缓存下来
总结
强缓存优先级是高于协商缓存的,主要是通过Cache-Control和Expires做简单的时间对比。
协商缓存主要是通过客户端带上标记去询问服务器,看缓存是否过期,服务器通过(Etag / If-None-Match)和(Last-Modified / If-Modified-Since)做类似文件版本的对比。
判断缓存的伪代码
访问服务器--开始
if (有缓存) {
if (Cache-Control的max-age时效过期了) {
// 缓存是过期了,但是万一服务器的文件没有改动呢,那不就可以继续用缓存啦,就执行下面逻辑
if (缓存文件的If-None-Match === 服务器文件的Etag) {
缓存还可以继续用,使用缓存
} else if (缓存文件的If-Modified-Since === 服务器文件的Last-Modified) {
// If-None-Match/Etag 优先级高于 If-Modified-Since/Last-Modified
缓存还可以继续用,使用缓存
} else {
文件被修改过啦,缓存没办法用了,重新请求资源文件
}
} else if (Expires的时效过期了) {
// Cache-Control的优先级高于Expires
文件被修改过啦,缓存没办法用了,重新请求资源文件
} else {
缓存还能用,直接使用
}
} else {
没有缓存,请求资源文件
}