缓存:

什么是缓存?

缓存就是一个资源副本。当我们向服务器请求资源后,会根据情况将资源 copy 一份副本存在本地,以方便下次读取。
缓存与本地存储 localStorage 、cookie 等不同,本地存储更多是数据记录,存储量较小,为了本地操作方便。而缓存更多是为了减少资源请求,多用于存储文件,存储量相对较大。

那缓存有啥用呢?

  1. 减少没必要请求
  2. 减少了时长从而优化用户体验,
  3. 也减少了流量消耗,
  4. 减轻了服务器的压力

    缓存有哪些呢?

    就浏览器而言,一般缓存我们分为四类,按浏览器读取优先级顺序依次为:1.Memory Cache、2.Service Worker Cache、3.HTTP Cache、4.Push Cache。

    浏览器缓存机制介绍与缓存策略


    Network 面板截图

    image.png

    我们给 size 这一栏一个特写:
    image.png
    大家注意一下非数字——即形如“(from xxx)”这样的描述——对应的资源,这些资源就是我们通过缓存获取到的。其中,“from memory cache”对标到 Memory Cache 类型,“from ServiceWorker”对标到 Service Worker Cache 类型。至于 Push Cache,这个比较特殊,是 HTTP2 的新特性。
  • 缓存位置:内存和硬盘
    • Memory Cache:内存缓存
      • 页面打开时
    • Disk Cache:硬盘缓存
      • 页面关闭时
  • 打开网页:查找 disk cache 中是否有匹配,如有则使用,如没有则发送网络请求
  • 普通刷新 (F5):因TAB没关闭,因此memory cache是可用的,会被优先使用,其次才是disk cache
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache,服务器直接返回 200 和最新内容

图层 1.png
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
内存缓存中有一块重要的缓存资源是 preloader 相关指令(例如 )下载的资源。它可以一边解析 js/css 文件,一边网络请求下一个资源。
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
绝大部分的缓存都来自 Disk Cache,在 HTTP 的协议头中设置。
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。
2. 检查--缓存【强缓存和协商缓存】 - 图6
HTTP 缓存是最主要、最具有代表性的缓存策略,也是每一位前端工程师都应该深刻理解掌握的性能优化知识点

HTTP 缓存 HTTP Cache(即 Cache-Control、expires 等字段控制的缓存)的理解

它又分为强缓存协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

  1. 强缓存:直接从本地副本比对读取,不去请求服务器,返回的状态码是 200
  2. 协商缓存:会去服务器比对,若没改变才直接读取本地缓存,返回的状态码是 304

    1.强缓存

    强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expires 和 cache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。
    命中强缓存的情况下,返回的 HTTP 状态码为 200 (如下图)。
    image.png

    强缓存的实现:从 expires 到 cache-control

    image.png

    1、expires :

    expires 是 HTTP1.0 中定义的缓存字段, 它是一个时间戳,如果本地时间小于 expires 设定的过期时间,那么就直接去缓存中取这个资源。由于时间戳是服务器来定义的
    问题:发送请求时是使用的客户端时间去和服务器时间对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期

2、cache-control

HTTP1.1 新增了 cache-control 字段来解决该问题,所以当 cache-control 和 expires 都存在时,cache-control 优先级更高。该字段是一个时间长度,单位秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control 主要有 max-age 和 s-maxage、public 和 private、no-cache 和 no-store 等值

  1. cache-control: public, max-age=3600, s-maxage=3600
  1. max-age 和 s-maxage
    两者是 cache-control 的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-age 和 s-maxage 都起作用,而且 s-maxage 的优先级高于 max-age。在代理服务器中,只有 s-maxage 起作用。 可以通过设置 max-age 为 0 表示立马过期来向服务器请求资源。

image.png

  1. public 和 private
    public 表示该资源可以被所有客户端和代理服务器缓存,而 private 表示该资源仅能客户端缓存。默认值是 private,当设置了 s-maxage 的时候表示允许代理服务器缓存,相当于 public。
  2. no-cache no-store
    no-cache 表示:不直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)

no-store 则更狠,完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不考虑缓存情况而是直接与服务器交互,所以当 no-cache 和 no-store 存在时会直接忽略 max-age 等。

3、pragma

no-cache 和 no-store,就顺便把 pragma 也讲了。他的值有 no-cache 和 no-store,表示意思同 cache-control,优先级高于 cache-control 和 expires,即三者同时出现时,先看 pragma -> cache-control -> expires。

强缓存流程

2. 检查--缓存【强缓存和协商缓存】 - 图10

2.协商缓存

  • 协商缓存:浏览器与服务器合作之下的缓存策略 浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。
  • 协商缓存主要包括 last-modified 和 etag。
  • 如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304(如下图)。

image.png

1、last-modified

last-modified :记录资源最后修改的时间
启用后,首次请求时随着 Response Headers(响应头) 返回增加一个 last-modified 字段,如下:

Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

随后我们每次请求时,求头中会带有 if-modified-since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT

服务端会对比该字段和资源的最后修改时间,若一致则证明没有被修改,告知浏览器可直接使用缓存并返回 304;若不一致则直接返回修改后的资源,并修改 last-modified 为新的值。
但 last-modified 有以下两个缺点:

  • 只要编辑了,不管内容是否真的有改变,都会以这最后修改的时间作为判断依据,当成新资源返回,从而导致了没必要的请求响应,而这正是缓存本来的作用即避免没必要的请求。
  • 时间的精确度只能到秒,如果在一秒内的修改是检测不到更新的,仍会告知浏览器使用旧的缓存。

    2、etag

    为了解决 last-modified 上述问题,有了 etag。
    Etag 是由服务器为每个资源生成的唯一的标识字符串 etag 会基于资源的内容编码生成一串唯一的标识字符串,只要内容不同,就会生成不同的 etag。启用 etag 之后,请求资源后的响应返回会增加一个 etag 字段,如下:

    etag: “FllOiaIvA1f-ftHGziLgMIMVkVw_”

当再次请求该资源时,请求头会带有 if-none-match 字段,值是之前返回的 etag 值,如:if-none-match:”FllOiaIvA1f-ftHGziLgMIMVkVw_”。服务端会根据该资源当前的内容生成对应的标识字符串和该字段进行对比,若一致则代表未改变可直接使用本地缓存并返回 304;若不一致则返回新的资源(状态码200)并修改返回的 etag 字段为新的值。
可以看出 etag 比 last-modified 更加精准地感知了变化,
Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
不过从上面也可以看出 etag 存在的问题,就是每次生成标识字符串会增加服务器的开销。所以要如何使用 last-modified 和 etag 还需要根据具体需求进行权衡。

协商缓存流程

2. 检查--缓存【强缓存和协商缓存】 - 图12

检查缓存 ->针对于静态资源文件【html\css\img\js】

强缓存 200 from cache:Expires / Cache-Control

  • 先检查本地是否有缓存,有且没过期。之直接本地获取,渲染【HTTP状态码200】
    + 如果没有或者过期,则重新向服务器发送请求,获取最新结果渲染,同时把本次结果缓存起来
    问题:本地缓存了文件,但是服务对应的资源文件更新了,我们如何保证获取的是最新的内容?:修改该资源的名称来实现

    1. 请求资源文件的时候设置时间戳
      1. 第一次
      2. 如果服务器资源有更新,再次发请求,保证时间戳不一样,这样就不会走本地的强缓存了,而是从新拉取最新的资源
    2. 文件HASH名 [现在常用,webpack可以做到]
      1. 第一次
      2. 如果服务器资源更新,文件名字重新HASH(webpack)

    c . 所以HTML文件永远不会去做强缓存
    里面的各种link / script才是大头,里面的资源链接要保证是最新的
    d . 设置Cache-Control:no-cache

    • 必须先与服务器(不走代理了,直接找源服务器)确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
  • 强缓存 & 协商缓存{针对于资源文件请求} & 本地存储{针对于数据请求}
    • 不论是强缓存还是协商缓存都是服务器端设置的,客户端浏览器自己会根据返回的一些信息,进行相关处理,无需前端单独设置啥东西
    • 优势:缓存处理是基于HTTP网络层进行优化的一
      +html是不能做强缓存的,只要保证html不做 其余资源可以及时更新:方法是 请求资源后面加上 时间戳,随机数,根据文件名生成HASH名 (webpack会搞定)

      http缓存控制流程:

      2. 检查--缓存【强缓存和协商缓存】 - 图13

2. 检查--缓存【强缓存和协商缓存】 - 图14

头部的区别

首先明确,http 的发展是从 http1.0 到 http1.1
而在 http1.1 中,出了一些新内容,弥补了 http1.0 的不足。
http1.0 中的缓存控制:

  • Pragma:严格来说,它不属于专门的缓存控制头部,但是它设置 no-cache 时可以让本地强缓存失效(属于编译控制,来实现特定的指令,主要是因为兼容 http1.0,所以以前又被大量应用)
  • Expires:服务端配置的,属于强缓存,用来控制在规定的时间之前,浏览器不会发出请求,而是直接使用本地缓存,注意,Expires 一般对应服务器端时间,如 Expires:Fri, 30 Oct 1998 14:19:41
  • If-Modified-Since/Last-Modified:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是 If-Modified-Since,而服务端的是 Last-Modified,它的作用是,在发起请求时,如果 If-Modified-Since 和 Last-Modified 匹配,那么代表服务器资源并未改变,因此服务端不会返回资源实体,而是只返回头部,通知浏览器可以使用本地缓存。Last-Modified,顾名思义,指的是文件最后的修改时间,而且只能精确到 1s 以内

http1.1 中的缓存控制:

  • Cache-Control:缓存控制头部,有 no-cache、max-age 等多种取值
  • Max-Age:服务端配置的,用来控制强缓存,在规定的时间之内,浏览器无需发出请求,直接使用本地缓存,注意,Max-Age 是 Cache-Control 头部的值,不是独立的头部,譬如 Cache-Control: max-age=3600,而且它值得是绝对时间,由浏览器自己计算
  • If-None-Match/E-tag:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是 If-None-Match,而服务端的是 E-tag,同样,发出请求后,如果 If-None-Match 和 E-tag 匹配,则代表内容未变,通知浏览器使用本地缓存,和 Last-Modified 不同,E-tag 更精确,它是类似于指纹一样的东西,基于 FileEtag INode Mtime Size 生成,也就是说,只要文件变,指纹就会变,而且没有 1s 精确度的限制。

Max-Age 相比 Expires?
Expires 使用的是服务器端的时间
但是有时候会有这样一种情况 - 客户端时间和服务端不同步
那这样,可能就会出问题了,造成了浏览器本地的缓存无用或者一直无法过期
所以一般 http1.1 后不推荐使用 Expires
而 Max-Age 使用的是客户端本地时间的计算,因此不会有这个问题
因此推荐使用 Max-Age。
注意,如果同时启用了 Cache-Control 与 Expires,Cache-Control 优先级高。
E-tag 相比 Last-Modified?
Last-Modified:

  • 表明服务端的文件最后何时改变的
  • 它有一个缺陷就是只能精确到 1s,
  • 然后还有一个问题就是有的服务端的文件会周期性的改变,导致缓存失效

而 E-tag:

  • 是一种指纹机制,代表文件相关指纹
  • 只有文件变才会变,也只要文件变就会变,
  • 也没有精确时间的限制,只要文件一遍,立马 E-tag 就不一样了

如果同时带有 E-tag 和 Last-Modified,服务端会优先检查 E-tag
各大缓存头部的整体关系如下图
2. 检查--缓存【强缓存和协商缓存】 - 图15