ZiuChen.github.io/docs/article/深入理解浏览器缓存机制.md
2023-02-05 15:07:58 +08:00

167 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 深入理解浏览器缓存机制
浏览器有两种缓存规则:强制缓存与协商缓存
1. 强制缓存:不会向服务器发送请求,直接从缓存中读取资源
2. 协商缓存向服务器发送请求服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存如果命中则返回304状态码并带上新的response header通知浏览器从缓存中读取资源
- 共同点:都是从客户端缓存中读取资源
- 不同点:强制缓存不会发请求,协商缓存会发请求
## 强制缓存
### 什么是强制缓存
浏览器在服务器发起真正请求前,先检查浏览器缓存:
- 如果命中缓存,且缓存未过期,那么直接使用缓存资源
- 如果未命中缓存,或缓存已过期失效,那么向服务器发出请求
### 强制缓存的规则
服务器通过向响应头添加`Expires``Cache-Control`字段来标识强制缓存的状态,浏览器会将这两个信息缓存到本地,后续有相同请求时,优先到浏览器缓存中检查资源是否到期。
其中`Cache-Control`优先级比`Expires`高,即:二者同时存在时,浏览器以`Cache-Control`为标准,检查缓存资源是否过期
### Expires与Cache-Control
#### Expires
`Expires`表示当前资源的失效时间它的值是一个HTTP-日期时间戳,例如:`Expires: Thu, 01 Dec 1994 16:00:00 GMT`
使用`Expires`存在一些弊端:
- 代表的是绝对时间,如果浏览器和服务器的时间不同步,会导致缓存目标时间存在偏差
- 如果服务端设置的日期格式不规范,那么等同于无缓存
- `Expires``HTTP/1.0`的字段,但是现在浏览器默认使用的是`HTTP/1.1`
在某些不支持HTTP1.1的环境下,`Expires`就会发挥用处
所以`Expires`其实是过时的产物,现阶段它的存在只是一种兼容性的写法
#### Cache-Control
如果在`Cache-Control`响应头设置了"max-age"或者"s-max-age"指令,那么`Expires`头会被忽略
设置`Cache-Control`的值有以下规则:
- 不区分大小写,但建议使用小写
- 多个指令以逗号分隔
- 具有可选参数,可以用令牌或者带引号的字符串语法
常用的指令:
- public所有内容都将被缓存即使是通常不可缓存的内容如POST请求
- private所有内容只有客户端可以缓存不能作为共享缓存即代理服务器不能缓存它这也是`Cache-Control`的默认取值
- no-cache客户端缓存内容但是是否使用缓存则需要经过协商缓存来验证决定
- no-store所有内容都不会被缓存即不使用强制缓存也不使用协商缓存即不使用任何缓存。
- max-age=xxx (xxx is numeric)缓存内容将在xxx秒后失效
举几个例子:
此次请求之后的600秒内如果浏览器再次发起请求那么直接使用缓存中的资源
```
Cache-Control: max-age=600
```
浏览器可以缓存资源,但每次使用缓存资源前都必须重新验证其有效性:
```
Cache-Control: no-cache
```
```
Cache-Control: max-age=0, must-revalidate
```
这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载
## 协商缓存
当浏览器检查本地的**强制缓存**已经失效后,浏览器携带该资源的**协商缓存**标识向服务器发起请求,由服务器根据缓存标识决定是否继续使用本地缓存。
- 协商缓存生效服务器返回304通知浏览器继续使用本地缓存
- 协商缓存失效服务器返回200与最新的请求资源
### 协商缓存的规则
服务器与浏览器通过两两成对的请求头来控制协商缓存:
- `Etag` `If-None-Match`
- `Last-Modified` `If-Modified-Since`
其中,`Etag``Last-Modified`是由服务器设置的响应头的字段,`If-None-Match``If-Modified-Since`则是浏览器向服务器发送的请求头的字段
#### Etag与Last-Modified
`Etag`是上一次加载资源时服务器返回的ResponseHeader是对该资源的一种唯一标识只要资源有变化`Etag`就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的`Etag`值放到RequestHeader里的`If-None-Match`里,服务器接受到`If-None-Match`的值后,会拿来跟该资源文件的`Etag`值做比较,如果相同,则表示资源文件没有发生改变,命中协商缓存。
- `Etag`由服务器生成,标志当前资源的唯一标识,一般包含大小、修改时间等信息
- `If-None-Match`浏览器缓存到本地的`Etag`
HTTP协议并未规定`Etag`的内容是如何生成的,但一般包含大小、修改时间等信息
Node.js下生成`Etag`的示例:
```js
// 根据文件的fs.Stats信息计算出etag
const genEtag = (stat) => {
const fileLength = stat.size // 文件的大小
const fileLastModifiedTime = stat.mtime.getTime() // 文件的最后更改时间
// 数字都用16进制表示
return `${fileLength.toString(16)}-${fileLastModifiedTime.toString(16)}`
}
```
#### Last-Modified与If-Modified-Since
`Last-Modified`是该资源文件最后一次更改时间服务器会在ResponseHeader里返回同时浏览器会将这个值保存起来在下一次发送请求时放到RequestHeader里的`If-Modified-Since`里,服务器在接收到后也会做比对,如果相同则命中协商缓存。
- `Last-Modified`由服务器添加,标志资源文件上次被修改的时间
- `If-Modified-Since`浏览器缓存到本地的`Last-Modified`
`If-None-Match`的优先级要高于`If-Modified-Since`,即:如果浏览器同时存在
#### 两种协商缓存的区别
- 精确度上,`Etag`要优于`Last-Modified``Last-Modified`的时间单位是秒如果某个文件在1秒内改变了多次那么他们的`Last-Modified`其实并没有体现出来修改但是Etag每次都会改变确保了精度如果是负载均衡的服务器各个服务器生成的`Last-Modified`也有可能不一致。
- 性能上,`Etag`要逊于`Last-Modified`,毕竟`Last-Modified`只需要记录时间,而`Etag`需要服务器通过算法来计算出一个值。
- 优先级上,服务器校验优先使用`Etag`
## 内存缓存与硬盘缓存
当我们打开一个新网页服务器返回200将资源发送给浏览器浏览器做本地缓存
当我们刷新标签页,浏览器从内存缓存获得资源
当我们关闭标签页重新打开,浏览器从硬盘缓存获得资源
- **内存缓存**(MemoryCache):内存缓存具有两个特点,分别是快速读取和时效性
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
- **硬盘缓存**(DiskCache)硬盘缓存则是直接将缓存写入硬盘文件中读取缓存需要对该缓存存放的硬盘文件进行I/O操作然后重新解析该缓存内容读取复杂速度比内存缓存慢。
在浏览器中浏览器会在js和图片等文件解析执行后直接存入内存缓存中那么当刷新页面时只需直接从内存缓存中读取(MemoryCache)而css文件则会存入硬盘文件中所以每次渲染页面都需要从硬盘读取缓存(DiskCache)。
## 用户对浏览器缓存的控制
- 地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制
- F5刷新浏览器会设置max-age=0跳过强缓存判断会进行协商缓存判断
- Ctrl+F5刷新跳过强缓存和协商缓存直接从服务器拉取资源
## 参考资料
[[稀土掘金] 彻底理解浏览器的缓存机制](https://juejin.cn/post/6844903593275817998)
[[微信公众号] 浏览器的缓存机制小结](https://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651226262&idx=1&sn=2128db200b88479face67ed8e095757c&chksm=bd4959128a3ed0041b43a5683c75c4b88c7d35fac909a59c14b4e9fc11e8d408680b171d2706&scene=21#wechat_redirect)
[[微信公众号] 浏览器缓存机制剖析](https://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651226347&idx=1&sn=6dbccc54406f0b075671884b738b1e88&chksm=bd49596f8a3ed079f79cda4b90ac3cb3b1dbdb5bfb8aade962a16a323563bf26a0c75b0a5d7b&scene=21#wechat_redirect)
[[RFC-9111] Expires](https://httpwg.org/specs/rfc9111.html#field.expires)
[[MDN] Expires](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Expires)
[[MDN] Cache-Control](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control)
[[MDN] ETag](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag)