• 缓存是一种性能优化方式,可以显著减少网络传输所带来的损耗
  • 对于一个数据请求,可以分为 网络请求,后端处理,浏览器响应。浏览器缓存可以帮助我们在第一和第三步中优化性能,比如说直接使用缓存而不发起请求,或者发起了请求但后端数据存储的数据和前端一致,那么没有必要将数据回传回来,这样就减少了响应数据

缓存分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器,协商缓存返回的状态码是 304 ,两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。

强缓存

浏览器中的缓存作用分为两种情况

  • 需要发送 HTTP 请求
  • 不需要

首先是检查强缓存,这个阶段不需要发送HTTP请求

如何检查?通过相应的字段进行
HTTP/1.0HTTP/1.1当中,这个字段是不一样的。在早期,也就是HTTP/1.0时期,使用的是Expires,而HTTP/1.1使用的是Cache-Control。让我们首先来看看Expires。

Expires(HTTP1.0)

Expires即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。比如下面这样:

Expires: Wed, 22 Nov 2019 08:41:00 GMT
表示资源在2019年11月22号8点41分过期,过期了就得向服务端发请求。

这个方式的问题:服务器的时间和浏览器的时间可能不一致,导致服务器返回的这个过期时间可能是不准确的。因此这种方式很快在后来的HTTP1.1版本中被抛弃了。

Cache-Control(HTTP1.1)

在HTTP1.1中,采用了一个非常关键的字段:Cache-Control。这个字段也是存在于服务端返回的响应头中,它和 Expires本质的不同在于它并没有采用具体的过期时间点这个方式,而是采用过期时长来控制缓存,对应的字段是max-age。比如这个例子:

Cache-Control:max-age=3600

代表这个响应返回后在3600秒,也就是一个小时之内可以直接使用缓存。

它其实可以组合非常多的指令,完成更多场景的缓存判断, 将一些关键的属性列举如下:public: 客户端和代理服务器都可以缓存。因为一个请求可能要经过不同的代理服务器最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。

private: 这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。
no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段
no-store:非常粗暴,不进行任何形式的缓存。
s-maxage:这和max-age长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间

值得注意的是,当ExpiresCache-Control同时存在的时候,Cache-Control会优先考虑。

当资源缓存时间超时了,也就是强缓存失效了,接下来就进入到 协商缓存 了 。

协商缓存

强缓存失效之后,浏览器在请求头中携带响应的 缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存
具体来说,这样的缓存tag分为两种 : Last-ModifiedETag。这两者各有优劣,并不存在谁对谁有绝对的优势,跟上面强缓存的两个 tag 不一样。

Last-Modified

即最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。
浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。
服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:

  • 如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
  • 否则返回304,告诉浏览器直接用缓存。

ETag

ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。

浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。

服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:

  • 如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
  • 否则返回304,告诉浏览器直接用缓存。

两者对比

  1. 精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
  • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
  • Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
  1. 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。

另外,如果两种方式都支持的话,服务器会优先考虑ETag

缓存位置

前面我们已经提到,当强缓存命中或者协商缓存中服务器返回304的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢?

浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是:

  • Service Worker
  • Memory Cache(内存缓存)
  • Disk Cache(硬盘缓存)
  • Push Cache(推送缓存)

Service Worker

独立的线程,我们可以在这个线程中缓存文件,在主线程需要的时候读取这里的文件,Service Worker使我们可以自由选择缓存哪些文件以及文件的匹配、读取规则,并且缓存是持续性的

Memory Cache

即内存缓存,效率上最快,存活时间最短。内存缓存不是持续性的,缓存会随着进程释放而释放。

Disk Cache

即硬盘缓存,相较于内存缓存,硬盘缓存的持续性和容量更优,它会根据HTTP header的字段判断哪些资源需要缓存

主要策略

  • 比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存
  • 内存使用率比较高的时候,文件优先进入磁盘

    Push Cache

    即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2 中的内容,虽然现在应用的并不广泛,但随着 HTTP/2 的推广,它的应用越来越广泛。关于 Push Cache,有非常多的内容可以挖掘,不过这已经不是本文的重点,大家可以参考这篇扩展文章

总结

首先通过 Cache-Control验证强缓存是否可用

  • 如果强缓存可用,直接使用
  • 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新
    • 若资源更新,返回资源和200状态码
    • 否则,返回304,告诉浏览器直接从缓存获取资源

image.png
文章