Fork me on GitHub

前端性能优化与原理实践之【浏览器缓存机制与缓存策略】

前端性能优化之浏览器缓存机制介绍与缓存策略剖析!

[TOC]

一、浏览器的缓存介绍

缓存可以减少网络 IO(input/output IO数据流)消耗,提高访问速度。可通过浏览器中简单的操作,完成前端的性能优化。缓存是利用了重复在客户端和服务端两端网络通信的资源,减少了网络请求和数据处理的时间

浏览器的缓存,主要从以下几个方面入手,按照请求资源的优先级排序如下:

  • Memory Cache
  • Service Worker Cache
  • HTTP Cache
  • Push Cache

二、HTTP 缓存机制

HTTP 缓存是日常开发最常用到的缓存机制,它分为强缓存协商缓存。强缓存的优先级高,只有当强缓存失败的情况下,才会有协商缓存。

2.1 强缓存

强缓存是通过 HTTP 头部的 ExpiresCache-Control 来控制的。强缓存中,当请求再次请求,浏览器就会检查请求头部的两个字段是否命中“强缓存”,如果有的话,直接在缓存中取出,不在进行网络请求。(下图为强缓存的请求)

img

2.2 强缓存:expire 到 cache-control

1、exprie

当服务器返回响应的时候,会在 HTTP 的响应头部设置资源的过期时间。

1
expires: Wed, 11 Sep 2019 16:12:18 GMT

当我们浏览器再次发起请求的时候,就会拿本地时间和这个过期时间作对比,如果小于过期时间,则在本地缓存中提取资源。

局限性:但是存在一个问题就是,本地的时间和服务器的时间不对应,而且本地的时间可以修改。

2、Cache-Control

到了 HTTP1.1 新增加了 Cache-Control 字段,代替 exprie 的任务,exprie 能完成的 Cache-Control 都能完成。但是 Cache-Control 有一个好处如下:

设置一个过期时间多少秒,资源会在这个秒数后消失。

1
cache-control: max-age=31536000

总结:Cache-Control 相对于 expires 更加准确,它的优先级也更高。当 Cache-Control 与 expires 同时出现时,我们以 Cache-Control 为准

3、max-ages-maxage

但在依赖各种代理的大型架构中,我们不得不考虑代理服务器的缓存问题。s-maxage 就是用于表示 cache服务器上(比如 cache CDN)的缓存的有效时间的,并只对 public 缓存有效。

s-maxage 优先级高于 max-age,两者同时出现时,优先考虑 s-maxage。如果 s-maxage 未过期,则向代理服务器请求其缓存内容

注意:s-maxage仅在代理服务器中生效,客户端中我们只考虑max-age。

publicprivate

两者的意思就是对资源是否缓存进行控制的。

如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果我们设置了 private,则该资源只能被浏览器缓存,private 为默认值

no-storeno-cache
  • no-store:不询问任何缓存,直接进行资源请求。
  • no-cache:每次请求不会询问浏览器是否有缓存时间,而是直接向服务器确认缓存的资源是否过期。

4、协商缓存

浏览器直接向服务器询问缓存,然后得到服务器的响应,进而判断从本地提取资源还是再次请求资源。最常见的就是 304 状态

Last-Modified

我们第一次请求过资源后,这个字段的时间戳就会随着 response Header 响应返回,

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

然后浏览器再次发起请求,就会携带这个时间戳,字段 If-Modified-Since:

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

服务器接收到之后,取出时间戳,判断与上次该资源修改的时间对比,如果资源改未改动,则返回 304 状态。Response Headers 不会再添加 Last-Modified字段。

Last-Modified 局限性

对于一些特殊的应用场景 Last-Modified 有局限性:

  • 编辑文件,但是没有改动文件,服务器认为我们变动了,所以会更改最后修改的时间,导致浏览器重新发起不必要的请求。
  • 在 100ms 发起改动,但是时间差的原因,感应不到 1 秒内的变动,所以改请求时却没有发起请求。

其实原因就是不能感应到文件内容的变化导致了上边的局限性。我们会使用 Etag 来进行代替。

Etag

Etag 是由服务器为每个资源生成的唯一的标识字符串,是由文件的编码组成的,一旦文件改变,该字符串就不同,所以 Etag 可以感应到文件的改变。

第一次请求返回的响应会在头部字段加上 Etag,

1
ETag: W/"2a3b-1602480f459"

然后浏览器再次发起请求,加上一个字段,服务器接收到然后作比对。

1
If-None-Match: W/"2a3b-1602480f459"

弊端: 生成字符串需要一定的时间,消耗服务器的资源,所以两者要在实际中进行权衡。两者同时存在时,后者优先级更高。

三、HTTP 缓存决策流程

在日常开发中,使用到的 HTTP 缓存解决图。

img

  • 当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;
  • 否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;
  • 否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。

四、MemoryCache —— 内存缓存

它是一种内存中的缓存,优先级最高的,响应速度最快,但是也是存储的时间最短的,一旦浏览器的 tab 关闭,缓存中的数据就不复存在了。

4.1 什么的资源会被存进去

并没有硬性的规定,但是根据浏览器的选择,比较小的文件优先被送内存缓存中去——这样比较节约。

  • Base64 格式的图片,几乎永远可以被塞进 memory cache;
  • 体积不大的 JS、CSS 文件,也有较大地被写入内存的几率;

五、Service Worker Cache

Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。可以实现离线缓存、消息推送和网络代理等功能

5.1 Service Worker 的生命周期

Service Worker 的生命周期包括三个阶段:(要想停止,必须主动进行停止)

  • install

  • active

  • working

PS:Server Worker 对协议是有要求的,必须以 https 协议为前提。

5.2 Push Cache

Chrome 工程师 Jake Archibald 的这篇 HTTP/2 push is tougher than I thought

Push Cache 是指 HTTP2 在 server push 阶段存在的缓存,

  • Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。
  • Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。