【前端基础进阶】浏览器的缓存机制

1、前言

缓存能够说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略能够缩短网页请求资源的距离,减小延迟,而且因为缓存文件能够重复利用,还能够减小带宽,下降网络负荷。
对于一个数据请求来讲,能够分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存能够帮助咱们在第一和第三步骤中优化性能。好比说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减小了响应数据。
接下来的内容中咱们将经过缓存位置、缓存策略以及实际场景应用缓存策略来探讨浏览器缓存机制。css

2、缓存位置

从缓存位置上来讲分为四种,而且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。前端

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

1.Service Worker
Service Worker 是运行在浏览器背后的独立线程,通常能够用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。由于 Service Worker 中涉及到请求拦截,因此必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其余内建的缓存机制不一样,它可让咱们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,而且缓存是持续性的。
Service Worker 实现缓存功能通常分为三个步骤:首先须要先注册 Service Worker,而后监听到 install 事件之后就能够缓存须要的文件,那么在下次用户访问的时候就能够经过拦截请求的方式查询是否存在缓存,存在缓存的话就能够直接读取缓存文件,不然就去请求数据。
当 Service Worker 没有命中缓存的时候,咱们须要去调用 fetch 函数获取数据。也就是说,若是咱们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。可是无论咱们是从 Memory Cache 中仍是从网络请求中获取的数据,浏览器都会显示咱们是从 Service Worker 中获取的内容。jquery

2.Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据确定比磁盘快,内存缓存虽然读取高效,但是缓存持续性很短,会随着进程的释放而释放。 一旦咱们关闭 Tab 页面,内存中的缓存也就被释放了。
那么既然内存缓存这么高效,咱们是否是能让数据都存放在内存中呢?
这是不可能的。计算机中的内存必定比硬盘容量小得多,操做系统须要精打细算内存的使用,因此能让咱们使用的内存必然很少。
当咱们访问过页面之后,再次刷新页面,能够发现不少数据都来自于内存缓存算法

clipboard.png

内存缓存中有一块重要的缓存资源是preloader相关指令(例如<link rel="prefetch">)下载的资源。总所周知preloader的相关指令已是页面优化的常见手段之一,它能够一边解析js/css文件,一边网络请求下一个资源。
须要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并不是仅仅是对URL作匹配,还可能会对Content-Type,CORS等其余特征作校验。chrome

3.Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,可是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在全部浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源须要缓存,哪些资源能够不请求直接使用,哪些资源已通过期须要从新请求。而且即便在跨站点的状况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,咱们会在下文进行详细介绍。
浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
关于这点,网上说法不一,不过如下观点比较靠得住:后端

对于大文件来讲,大几率是不存储在内存中的,反之优先
当前系统内存使用率高的话,文件优先存储进硬盘浏览器

4.Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,而且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并不是严格执行HTTP头中的缓存指令。
Push Cache 在国内可以查到的资料不多,也是由于 HTTP/2 在国内不够普及。这里推荐阅读Jake Archibald的 HTTP/2 push is tougher than I thought 这篇文章,文章中的几个结论:缓存

全部的资源都能被推送,而且可以被缓存,可是 Edge 和 Safari 浏览器支持相对比较差
能够推送 no-cache 和 no-store 的资源
一旦链接被关闭,Push Cache 就被释放
多个页面可使用同一个HTTP/2的链接,也就可使用同一个Push Cache。这主要仍是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不一样的tab标签使用同一个HTTP链接。
Push Cache 中的缓存只能被使用一次
浏览器能够拒绝接受已经存在的资源推送
你能够给其余域名推送资源安全

若是以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,一般浏览器缓存策略分为两种:强缓存和协商缓存,而且缓存策略都是经过设置 HTTP Header 来实现的。性能优化

3、缓存过程分析

浏览器与服务器通讯的方式为应答模式,便是:浏览器发起HTTP请求 – 服务器响应该请求,那么浏览器怎么肯定一个资源该不应缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来肯定的。具体过程以下图:

clipboard.png

第一次发起HTTP请求

由上图咱们能够知道:

浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

以上两点结论就是浏览器缓存机制的关键,它确保了每一个请求的缓存存入与读取,只要咱们再理解浏览器缓存的使用规则,那么全部的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便你们理解,这里咱们根据是否须要向服务器从新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。

4、强缓存

强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中能够看到该请求返回200的状态码,而且Size显示from disk cache或from memory cache。强缓存能够经过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
1.Expires
缓存过时时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,须要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过时时间前浏览器能够直接从浏览器缓存取数据,而无需再次请求。
Expires 是 HTTP/1 的产物,受限于本地时间,若是修改了本地时间,可能会形成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过时,须要再次请求。

2.Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。好比当Cache-Control:max-age=300时,则表明在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
Cache-Control 能够在请求头或者响应头中设置,而且能够组合使用多种指令:

clipboard.png

  • public:全部内容都将被缓存(客户端和代理服务器均可缓存)。具体来讲响应可被任何中间节点缓存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy能够缓存资源,好比下次再请求同一资源proxy1直接把本身缓存的东西给 Browser 而再也不向proxy2要。
  • private:全部内容只有客户端能够缓存,Cache-Control的默认取值。具体来讲,表示中间节点不容许缓存,对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,本身不缓存任何数据。当下次Browser再次请求时proxy会作好请求转发而不是自做主张给本身缓存的数据。
  • no-cache:客户端缓存内容,是否使用缓存则须要通过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式作前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。须要注意的是,no-cache这个名字有一点误导。设置了no-cache以后,并非说浏览器就再也不缓存数据,只是浏览器在使用缓存数据时,须要先确认一下数据是否还跟服务器保持一致。
  • no-store:全部内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • max-age:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效
  • s-maxage(单位为s):同max-age做用同样,只在代理服务器中生效(好比CDN缓存)。好比当s-maxage=60时,在这60秒中,即便更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。若是存在s-maxage,则会覆盖掉max-age和Expires header。
  • max-stale:能容忍的最大过时时间。max-stale指令标示了客户端愿意接收一个已通过期了的响应。若是指定了max-stale的值,则最大容忍时间为对应的秒数。若是没有指定,那么说明浏览器愿意接收任何age的响应(age表示响应由源站生成或确认的时间与当前时间的差值)。
  • min-fresh:可以容忍的最小新鲜度。min-fresh标示了客户端不肯意接受新鲜度很少于当前的age加上min-fresh设定的时间之和的响应。

clipboard.png

从图中咱们能够看到,咱们能够将多个指令配合起来一块儿使用,达到多个目的。好比说咱们但愿资源能被缓存下来,而且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。

3.Expires和Cache-Control二者对比
其实这二者差异不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,二者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。因此Expires实际上是过期的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断是否缓存的依据来自因而否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会致使加载文件不是服务器端最新的内容,那咱们如何获知服务器端内容是否已经发生了更新呢?此时咱们须要用到协商缓存策略。
5、协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有如下两种状况:

协商缓存生效,返回304和Not Modified

加粗文字

协商缓存失效,返回200和请求结果

clipboard.png

协商缓存能够经过设置两种 HTTP He
clipboard.png
Modified 和 ETag 。

1.Last-Modified和If-Modified-Since

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;

Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT

浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,因而添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,若是没有变化,返回304和空的响应体,直接从缓存读取,若是If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,因而返回新的资源文件和200

clipboard.png

可是 Last-Modified 存在一些弊端:

若是本地打开缓存文件,即便没有对文件进行修改,但仍是会形成 Last-Modified 被修改,服务端不能命中缓存致使发送相同的资源
由于 Last-Modified 只能以秒计时,若是在不可感知的时间内修改完成文件,那么服务端会认为资源仍是命中了,不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,可否能够直接根据文件内容是否修改来决定缓存策略?因此在 HTTP / 1.1 出现了 ETag 和If-None-Match

2.ETag和If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个惟一标识(由服务器生成),只要资源有变化,Etag就会从新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只须要比较客户端传来的If-None-Match跟本身服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。若是服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(固然也包括了新的ETag)发给客户端;若是ETag是一致的,则直接返回304知会客户端直接使用本地缓存便可。

clipboard.png

3.二者之间对比:

首先在精确度上,Etag要优于Last-Modified。

Last-Modified的时间单位是秒,若是某个文件在1秒内改变了屡次,那么他们的Last-Modified其实并无体现出来修改,可是Etag每次都会改变确保了精度;若是是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只须要记录时间,而Etag须要服务器经过算法来计算出一个hash值。
第三在优先级上,服务器校验优先考虑Etag

6、缓存机制

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么表明该请求的缓存失效,返回200,从新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。具体流程图以下:

clipboard.png
rd.png](/img/bVbo7p7)
到这里,不知道你是否存在这样一个疑问:若是什么缓存策略都没设置,那么浏览器会怎么处理?
对于这种状况,浏览器会采用一个启发式的算法,一般会取响应头中的 Date 减去 Last-Modified 值的 10% 做为缓存时间。

7、实际场景应用缓存策略

1.频繁变更的资源

Cache-Control: no-cache

对于频繁变更的资源,首先须要使用Cache-Control: no-cache 使浏览器每次都请求服务器,而后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的作法虽然不能节省请求数量,可是能显著减小响应数据大小。
2.不常变化的资源

Cache-Control: max-age=31536000

一般在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器以后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就须要在文件名(或者路径)中添加 hash, 版本号等动态字符,以后更改动态字符,从而达到更改引用 URL 的目的,让以前的强制缓存失效 (其实并未当即失效,只是再也不使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

8、用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操做时,会触发怎样的缓存策略。主要有 3 种:

打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。若有则使用;如没有则发送网络请求。普通刷新 (F5):由于 TAB 并无关闭,所以 memory cache 是可用的,会被优先使用(若是匹配的话)。其次才是 disk cache。强制刷新 (Ctrl + F5):浏览器不使用缓存,所以发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。

相关文章
相关标签/搜索