每个浏览器都实现了 HTTP 缓存,我们通过浏览器使用HTTP协议与服务器交互的时候,浏览器就会根据一套与服务器约定的规则进行缓存工作。
缓存的几种情况
-
第一次请求,浏览器没有缓存
1.1 浏览器发起 HTTP 请求
1.2 因为是第一次请求,所以本地没有请求结果的缓存和标识
1.3 于是浏览器向服务器发起 HTTP 请求
1.4 服务器返回浏览器请求的结果并且在请求头中携带资源的缓存规则
1.5 浏览器拿到请求的结果,并将结果和缓存标识存入浏览器缓存
-
第二次请求,浏览器缓存未失效
2.1 浏览器发起 HTTP 请求
2.2 发现缓存中已经有了该请求的返回结果,并且在有效期内,则直接使用缓存中的结果,不再向服务器发送 HTTP 请求
-
第三次请求,浏览器缓存认为自己失效了,然后询问服务器自己到底失没失效,服务器说:你没失效
3.1 浏览器发起 HTTP 请求
3.2 发现缓存中已经有了该请求的返回结果,但是已经失效了,于是只返回了该请求结果的缓存标识
3.3 浏览器携带着缓存标识向服务器发起 HTTP 请求
3.4 服务器拿到缓存标识与本地进行对比,发现没有更新,于是返回 HTTP code 304 告诉浏览器:我没有更新,你可以继续使用缓存的结果
3.5 浏览器得知缓存没有更新后,从缓存中获取该请求的结果
3.6 返回缓存的请求结果
-
第四次请求,浏览器缓存认为自己失效了,然后询问服务器自己到底失没失效,服务器说:你确实是失效了
4.1 浏览器发起 HTTP 请求
4.2 发现缓存中已经有了该请求的返回结果,但是已经失效了,于是只返回了该请求结果的缓存标识
4.3 浏览器携带着缓存标识向服务器发起 HTTP 请求
4.4 服务器拿到缓存标识与本地进行对比,发现确实更新过,于是返回 HTTP code 200 和请求的最新结果
4.5 浏览器将拿到的新结果存储到浏览器缓存中
缓存控制
HTTP/1.1 用 Cache-Control 请求头来区分缓存机制的支持情况。通过不同的值来定义缓存策略。
-
没有缓存 no-store
缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。
-
缓存但重新验证 no-cache
此方式下,每次有请求发出时,缓存会将此请求发到服务器(该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。
-
私有和公共缓存 private/public
public 指令表示该响应可以被任何中间人(比如中间代理、CDN等)缓存。private 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
-
过期 max-age=<seconds>
过期机制中,最重要的指令是
max-age=<seconds>,单位秒,表示资源能够被缓存(保持新鲜)的最大时间。相对 Expires 设置绝对时间而言,max-age 是距离请求发起的时间的秒数的相对时间。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。 -
验证方式 must-revalidate
意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。
HTTP/1.0 中使用 Pragma 来区分缓存机制的支持情况。
请求中包含 Pragma 的效果跟在头信息中定义 Cache-Control: no-cache 相同,但是 HTTP 的响应头没有明确定义这个属性,所以它不能拿来完全替代 HTTP/1.1 中定义的 Cache-control 头。
新鲜度
当我们在浏览器缓存中读取缓存时,是如何判断缓存是否在有效期内的呢?
在 HTTP 1.0 中,使用请求头中的 Expires 字段来控制缓存是否失效。Expires 请求头的值为服务器返回的该请求结果缓存到期的时间。当下次请求时间小于这个时间,则缓存有效;当下次请求时间大于这个时间,则缓存失效。

但是因为客户端与服务端的时间容易产生误差,就会造成缓存失效。所以在 HTTP 1.1 中,使用 Cache-COntrol 替代了 Expires。
在 HTTP 1.1 中,使用请求头中的 Cache-Control 字段来控制缓存是否失效。与 Expires 不同的是,Cache-Control 返回的为 max-age=<seconds>,是相对值,表示缓存会在 <seconds> 时间之后失效,也就是说在 <seconds> 之内再次发起请求,则会直接使用缓存结果。

在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control 相比于 expires 是更好的选择。所以同时存在时,只有 Cache-Control 生效。
服务器是怎么告知浏览器是否在有效期内呢?
当第一次向服务器请求时,服务器将缓存标识放到请求头 Last-Modified 中返回。Last-Modified 表示该资源在服务器最后修改的时间。

当本地缓存失效后,再次向服务器发起请求会将 Last-Modified 中的值放到请求头 If-Modified-Since 中。当服务器收到该请求,发现请求头含有 If-Modified-Since 字段,则会根据 If-Modified-Since 的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于 If-Modified-Since 的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
除了上面可使用的 Last-Modified / If-Modified-Since,还提供了 Etag / If-None-Match 方式来判断缓存是否失效。Etag 为服务器返回的响应头中的字段,为服务器生成的当前资源的唯一标识。
缓存位置
浏览器的缓存放在了哪里,我们怎么能判断出来请求使用了浏览器的缓存呢?

我们在观察 network 时发现,状态码为灰色的请求,它的 Size 值中出现了 memory cache 和 disk cache,也就是说这些请求的结果是从内存或者硬盘中读取的。
那么 memory cache 和 disk cache 有什么区别呢?
内存缓存 memory cache
内存缓存具有两个特点,分别是快速读取和时效性。
- 快速读取:内存缓会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取
- 时效性:一旦该进程关闭,则该进程的内存则会清空
硬盘缓存 disk cache
硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
在浏览器中,浏览器会在 js 和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(memory cache);而 css 文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(disk cache)。