参考博客:彻底理解浏览器的缓存机制、浏览器缓存机制
1、缓存的分类
浏览器缓存:
- 浏览器缓存是将文件保存在客户端,在同一个会话过程中会检查缓存的副本是否足够新,在后退网页时,访问过的资源可以从浏览器缓存中拿出使用。
- 常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括request ,method和目标URI(一般只有GET请求才会被缓存)。
- 浏览器在每次GET URL时都会先检查URL对应的缓存,除非你指定不使用缓存(强制刷新或者Disable Cache等)
为什么需要缓存:
- 减少网络带宽消耗
- 降低服务器压力
- 减少网络延迟,使页面的打开速度更快,增加用户体验
2、缓存规则定义位置
可以在 HTTP协议头 和HTML页面的 meta标签 中定义。
1)meta标签定义:
<!- Pragma 是 http1.0 版本中给客户端设定缓存方式之一 ->
<meta http-equiv="Pragma" content="no-cache">
// 上述代码的含义:浏览器当前页面不被缓存,每次访问都向服务器请求。
2)http协议头定义:
- 与缓存有关的消息报头有
expires,cache-control,Last-Modified,If-modified-since,Etag,If-none-match
等。
3、缓存过程
浏览器根据 响应报文 中HTTP头的 缓存规则 ,决定是否缓存结果,如果是则将请求结果和缓存标识存入浏览器缓存中。
由上图可知(浏览器缓存机制的关键点):
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
4、缓存位置
-
Service Worker
-
Memory Cache
-
Disk Cache
-
Push Cache
1)Service Worker
使用 Service Worker的话,传输协议必须为 HTTPS。
- 因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
2)Memory Cache(内存缓存)
读取速度快,缓存持续性很短,会随着进程的释放而释放。 一旦关闭 Tab 页面,内存中的缓存也会被释放。
需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。
3)Disk Cache(硬盘缓存)
硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
4)Push Cache(推送缓存)
Push Cache是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
【补充】
-
在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);
-
而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
5、缓存分类
1)强制缓存
控制字段:
- HTTP1.0:
Expires
- HTTP1.1 :
Cache-Control
(优先级更高)
判断过程:
- 请求再次发起 -> 浏览器根据
expires
和cache-control
判断目标资源是否命中"强缓存" -> 若命中,直接从缓存获取资源,不再与服务器发生通讯。 - 如果
cache-control
与expires
同时存在,以cache-control
为主,继续使用expires
的目的就是向下兼容。
(1)Cache-Control
-
public:所有内容都将被缓存(客户端和代理服务器都可缓存)
-
private:所有内容只有客户端可以缓存,Cache-Control的默认取值
-
no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
-
no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
-
max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
-
must-revalidate: 强制浏览器严格遵守你设置的cache规则
-
proxy-revalidate: 强制proxy严格遵守你设置的cache规则
(2)Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
一个例子:
三种情况:
第一种,
第二种,
第三种,
2)协商缓存
协商缓存就是 强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存 的过程。
控制字段(需配合cache-control
使用):
- HTTP1.0:
Last-Modified
&&If-Modified-Since
- HTTP1.1 :
Etag
&&If-None-Match
(优先级更高)
(1)Last-modified / If-Modified-Since
服务器端文件的最后修改时间,需要和cache-control
共同使用,是检查服务器端资源是否更新的一种方式。
流程:
- 首次请求
- 服务器告知启用协商缓存规则,并在响应头中带上
Last-Modified
,告知缓存到期时间 - 随后的每次请求,请求头上都会携带
If-Modified-Since
,该值等于上一次响应头中的Last-Modified
的值 - 服务器收到
If-Modified-Since
后,会将该属性的值与服务器上资源的最后修改时间进行匹配,从而判断资源是否发生了变化 - 如果发生变化会返回一个完整的响应内容,在响应头中添加新的
Last-Modified
值,状态码为200
,资源为服务器最新资源;否则,只返回 header 部分,状态码为304
,响应头不会再添加Last-Modified
,并使用缓存
(2)ETag / If-None-Match
根据实体内容生成一段hash字符串,标识资源的状态(唯一标识符),由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改。
流程:
- 首次请求
- 服务器启用协商缓存情况下,会在响应头中带上
Etag
- 随后每次请求,请求头上都会带上
If-None-Match
,该值等于上一次响应头中的Etag
的值 - 服务器收到
If-None-Match
后,会进行比对,从而判断资源是否发生变化 - 如果变化返回一个完整响应内容,在响应头上添加新的
Etag
值,否则返回304
, 响应头不会在添加Etag
两者区别:
Last-Modified
是根据文件的修改时间作唯一标识,并且修改时间只能精确到秒级(同1s修改多次,无法生成新的Last-Modified
)。Etag
是根据资源内容在服务器端的唯一标识符,能够更加准确的控制缓存。
两种情况:
- 第一种,协商缓存生效,返回304
- 第二种,协商缓存失效,返回200和请求结果结果
总的流程图:
6、缓存实现方式
方式一:客户端指定
- request header 携带cache-control
- 服务端接收到 header cache-control字段,并且使用该字段设置response 的 cache-control
- 浏览器接收到response header,根据cache-control 设置缓存
方法二:服务端指定
- 服务端接收到请求,设置response 的 cache-control并返回response
- 浏览器接收到response header,根据cache-control 设置缓存
其他情况
- 浏览器可以根据用户操作直接跳过缓存,比如勾选了控制台的Disable cache
7、不能被缓存的请求
- HTTP信息头中包含
Cache-Control:no-cache,pragma:no-cache(HTTP1.0)
,或Cache-Control:max-age=0
等告诉浏览器不用缓存的请求 - 需要根据
Cookie
,认证信息等决定输入内容的动态请求是不能被缓存的 - 经过
HTTPS
安全加密的请求(若存储位置为Service Worker,传输协议必须为HTTPS
) POST
请求无法被缓存(重点)HTTP
响应头中不包含Last-Modified/Etag
,也不包含Cache-Control/Expires
的请求无法被缓存
8、用户操作行为与缓存
解释:
- 强制刷新 – 浏览器将绕过各种缓存(强制缓存和协商缓存),直接让服务器返回最新的资源
- 普通刷新 – 浏览器将绕过本地缓存来发送请求到服务器,此时,协商缓存是有效的
- 回车或转向 – 当在地址栏上输入回车或者按下跳转按钮的时候,所有缓存都生效