参考内容:
彻底理解浏览器的缓存机制
彻底弄懂HTTP缓存机制及原理

前端开发人员有大部分时间都在调整页面样式,如果页面没有按照自己预期的样式显示,可能想到的第一个解决方案就是清一下浏览器缓存,HTTP 缓存机制作为 Web 性能优化的重要手段,也应该是 Web 开发人员必备的基础知识。我们常说的浏览器缓存机制也就是 HTTP 缓存机制,它是根据 HTTP 报文的缓存标识运行的,所以首先要对 HTTP 报文有一个简单的了解。

HTTP 报文

HTTP 报文是浏览器和服务器间进行通信时所发的响应数据,所以 HTTP 报文分为请求(Request)报文和响应(Response)报文两种,浏览器向服务器发送的是请求报文,而服务器向浏览器发送的是响应报文。HTTP 请求报文由请求行、请求头、请求体组成,响应报文则由状态行、响应头、响应正文组成,与缓存有关的规则信息则都包含在请求头和响应头中。

缓存概述

浏览器与服务器通过请求响应模式来通信,当浏览器第一次向服务器发送请求并拿到结果后,会根据响应报文中的缓存规则来决定是否缓存结果,其简单的流程如下图:

浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果和缓存标识,而且每次拿到响应数据后都会将该结果和缓存标识存入缓存中。HTTP 缓存的规则有多种,我们可以根据是否需要重新向服务器发起请求这一维度来分类,即有强制缓存协商缓存两类,也有人把协商缓存叫对比缓存。

强制缓存

我们先自己想一下,使用缓存是不是会有下面几种情况出现。

  • 存在所需缓存并且未失效:直接走本地缓存即可;强制缓存生效;

  • 存在所需缓存但已失效:本地缓存失效,携带着缓存标识发起 HTTP 请求;强制缓存失效,使用协商缓存;

  • 不存在所需缓存:直接向服务器发起 HTTP 请求;强制缓存失效。

控制强制缓存的字段分别是ExpiresCache-Control,并且Cache-Control的优先级高于Expires

Expires

Expires是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回的该缓存到期时间,即下一次请求时,请求时间小于Expires值,就直接使用缓存数据。到了 HTTP/1.1,Expires已经被Cache-Control替代了。

Expires被替代的原因是因为服务端和客户端的时间可能有误差(比如时区不同或者客户端与服务端有一方时间不准确),这就会导致缓存命中误差,强制缓存就变得毫无意义。

Cache-Control

Cache-Control是 HTTP/1.1 中最重要的规则,主要取值为:

取值 规则
public 所有内容都可以被缓存,包括客户端和代理服务器,纯前端可认为与private一样。
private 所有内容只有客户端可以缓存,Cache-Control的默认值。
no-cache 客户端可以缓存,但是是否缓存需要与服务器协商决定(协商缓存)
no-store 所有内容都不会被缓存,既不是用强制缓存,也不使用协商缓存,为了速度快,实际上缓存越多越好,所以这个慎用
max-age=xxx 缓存内容将在 xxx 秒后失效

我们可以看看下面这个例子,可以从截图中看到Expires是一个绝对值,而Cache-Control是一个相对值,此处为max-age=3600,即 1 小时后失效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于Expires是更好的选择,所以同时存在时只有Cache-Control生效。

协商缓存

协商缓存,顾名思义就是需要双方通过协商来判断是否可以使用缓存。强制缓存失效后,浏览器带着缓存标识向服务器发起请求,由服务器根据缓存标识决定是否可以使用缓存,那自然而然就有协商缓存生效和协商缓存不生效两种情况了。

上图是协商缓存生效的流程,如果协商缓存不生效则返回的状态码为 200。协商缓存的标识也是在响应报文的响应头中返回给浏览器的,控制协商缓存的字段有Last-Modified / If-Modified-SinceEtag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高,所以同时存在时只有Etag / If-None-Match生效。

Last-Modified / If-Modified-Since

你可以往上翻一翻,看一下那张响应报文截图,其中有一个Last-Modified字段,它的值是该资源文件在服务器最后被修改的时间。

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值。服务器收到该请求后,发现该请求头有If-Modified-Since字段,则会将If-Modified-Since与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为 200;否则则返回 304,代表资源无更新,可继续使用缓存文件。

Etag / If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个由服务器生成的唯一标识。

If-None-Match则是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,如果一致则就返回 304,代表资源无更新,可以继续使用缓存文件;否则重新返回资源文件,状态码为200,

disk cache 与 memory cache

我们可以通过浏览器调试工具查看强制缓存是否生效,如下图所示,状态码为灰色的请求就代表使用了强制缓存,请求对应的 size 显示了该缓存存放的位置,那么什么时候用 disk 什么时候用 memory 呢?

猜都能猜出来,肯定是优先使用内存(memory)中的缓存,然后才用硬盘(disk)中的缓存。

内存缓存具有快速读取的特点,它会将编译解析后的文件直接存入该进程的内存中,但是一旦进程关闭了,该进程的内存就会被清空,所以如果你将一个网页关闭后再打开,那么缓存都会走硬盘缓存,而如果你只是刷新网页,那有部分缓存走的就是内存缓存。

浏览器一般会再 js 和图片等文件解析执行后直接存入内存缓存中,当刷新页面时,这部分文件只需要从内存缓存中读取即可,而 css 文件则会存入硬盘中,所以每次渲染页面都需要从硬盘中读取文件。

总结

到这里偷懒一下子了,找到人家画的一张图,看图就行了。