# 深入理解浏览器缓存机制 浏览器有两种缓存规则:强制缓存与协商缓存 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)