深入理解浏览器缓存机制
缓存是提升网页性能最好的方式之一,开发者只需要控制几个字段就可以减少大量的数据传输和低效的网络请求。
目前几乎所有的缓存控制字段是来源于后端服务器的响应,这里不包括浏览器的存储如 Storage
,IndexedDB
。为什么前端工程师还需要学网络http部分?
答案是前端工程师才更了解业务的具体场景是什么,如图片加载缓存优化。前端不直接控制缓存字段,而是与后端沟通定义当前场景的缓存策略,只有了解了缓存有哪些策略才能够针对某一场景的缓存进行优化。
由于之前一上来就是各种复杂的缓存组合策略,所以学而退,退而学。参考前辈的笔记,从主流的控制字段结合分析缓存字段的发展,相信会有入门理解的收获。
一个数据请求过程可以分成发起网络请求,服务器处理,浏览器响应三个部分。浏览器缓存能够在第一、第三阶段,也就是发起网络请求、浏览器响应优化性能。比如第一阶段直接使用缓存不发起请求,第三阶段浏览器发起请求了但是请求资源未变化,不需要重新传送,减少响应数据。
学习缓存需要认识缓存位置,缓存策略以及实际场景如何应用缓存策略。
1. 缓存位置
从缓存位置上来说缓存分为四种,并各有优先级,按照优先级查找缓存,找到且符合要求即结束查找并返回,注意Chrome中,Memory Cache优先级是最高的
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
1.1 Service Worker(可先不深入,但需要了解)
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。 Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的 。
Service Worker 检查资源是否存在其缓存中,并根据其编程的缓存策略决定是否返回资源。这个操作不会自动发生,需要在注册的 Service Worker 中定义 fetch
事件去拦截并处理网络请求,这样才能命中 Service Worker 缓存而不是网络或者 HTTP 缓存。
简单的理解:前端定义规则手动控制文件的缓存,利用fetch
事件,在发送网络请求前拦截并检查本地是否缓存了数据,以决定是否发起网络请求。相信作为一名前端工程师会有本地存储 token 的经验,发起网络请求前检查本地的 token,如果 token 不存在可以直接返回,不发起请求,Service Worker就是类似的工作原理。
Service Worker 看起来也是一个缓存技术,为什么要使用 Service Worker?
答案:
提高WebApp的离线缓存能力,缩小 WebApp 与 NativeApp 之间差距。在无网情况下发起一个网络请求,网页会一直加载,最后跳出网络未连接的提示,而原生的App即使是在无网络情况下如单机游戏,还是正常运行。
事件同步:即使关闭了当前 Web 页面也可以正常工作。如 Web 邮件客户端通知
1.2 Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了 。
既然内存缓存效率那么高,是不是可以将所有的缓存都使用内存来存储呢?
这是不可能的。首先从容量上看,计算机内存容量比硬盘小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。其次,内存在断电情况下不保存数据的,所以不可能将所有的数据都放在内存。
当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存(Size字段)
古老的浏览器解析一个网页的流程是串行的,网络请求下载,解析,网络请求下载另外一个文件,解析。现代的浏览器都是并行下载的,即一边解析,一边可能几个线程在下载不同的资源文件。
内存缓存中有一块重要的缓存资源是 preloader
、preload
相关指令(例如<link rel="prefetch">
)下载的资源。总所周知 preloader 的相关指令已经是页面优化的常见手段之一,它可以一边解析 js/css 文件,一边网络请求下一个资源,并不一定只是一个网络请求,可能同时存在多个线程在下载资源。关于缓存字段,将在下文详细介绍。
需要注意的是:内存缓存在缓存资源时并不关心 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还可能会对 Content-Type , CORS 等其他特征做校验 。
简单总结:目前几乎所有的 HTTP缓存控制字段 都来源于后端服务的响应,在Chrome中,内存缓存的优先级是最高的。内存缓存不关心HTTP响应的缓存头字段,同时资源的匹配也并非只是对URL做匹配,还可能会对Content-TYpe,CORS等其他特征做校验。
1.3 Disk Cache
Disk Cache 是存储在硬盘上的缓存,虽然读取速度较慢,但是什么都能存储到磁盘中, 比之 Memory Cache 胜在容量和存储时效性上 ,能够长时间的保存缓存。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的缓存字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
例如:常见的JQuery压缩包,在Disk Cache缓存后,所有使用JQuery的站点都可以使用本地缓存的JQuery。
绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。
那么浏览器什么时候会将资源缓存到硬盘中呢?关于这点,网上说法不一,不过以下观点比较靠得住:
- 对于大文件来说,大概率是不存储在内存中的,反之优先
- 当前系统内存使用率高的话,文件优先存储进硬盘
简单总结 Disk Cache
- 容量大
- 时间长
- 跨站点
- 覆盖范围最广
1.4 Push Cache(需要了解,但在Service Worker之后)
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 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
2. 缓存过程分析(缓存策略)
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。那么为了性能上的考虑,大部分的接口都应该选择好缓存策略, 通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的 。
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求, 那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢 ?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存, 即浏览器对资源缓存的处理是根据第一次请求资源时返回的响应头来确定的 。具体过程如下图:
无论是什么时候发起的请求,浏览器首先匹配浏览器缓存,如果没有缓存命中或者缓存过期才会发送网络请求请求最新资源。在资源响应后,浏览器会根据HTTP响应中的缓存字段来控制缓存方式(若无缓存字段,则为默认缓存方式,默认缓存方式在下文介绍)。
由上图我们可以知道:
- 浏览器每次发起请求,都先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,浏览器查找和存入确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。
3. 强缓存和协商缓存
强缓存和协商缓存两个阶段并不容易理解,读者需要细心阅读并反复思考。
按照是否需要向服务器重新发起HTTP请求将缓存的过程分为两个部分,强缓存阶段和协商缓存阶段。Memory Cache 和 Disk Cache 都属于强缓存。现代浏览器缓存存储图像和网页等(主要在磁盘上),而你的操作系统缓存文件可能大部分在内存缓存中。
注意:强缓存和协商缓存是一个阶段,而不是指具体的缓存位置或形式。
浏览器每次发送请求前都会检查自身缓存,称为强缓存阶段,具有不存在、存在两种情况
缓存不存在,属于强缓存失效阶段,请求新资源
缓存存在,可以分成未过期、已过期两种情况
缓存未过期,属于强缓存生效,那么结束并返回对应缓存
缓存已过期,属于强缓存失效,那么进入协商缓存阶段,可以分成生效,失效两种情况
- 服务器资源未修改,返回 304,属于协商缓存生效
- 服务器资源已修改,返回 200,属于协商缓存失效
从以上分析中可以得到结论
每一次网络请求都会进入强缓存阶段,当 缓存存在 且 缓存已过期 时才能进入协商缓存阶段。缓存不存在时发起的网络请求不属于协商缓存阶段,这一点和后续的协商缓存容易混淆,也请记住缓存不存在能表达的两种情况,第一次请求和请求后被删除。特殊的,当缓存存在时,强缓存中有一个字段 Cache-Control: no-cache
可以直接使强缓存失效并进入协商缓存,将在强缓存中详细介绍。
简单总结:浏览器发送请求前检查自身缓存,属于强缓存阶段;缓存存在 且 缓存已过期,需要重新发起网络请求则是协商缓存阶段。
3.1 强缓存
强缓存:不会向服务器重新发送请求,直接从缓存中读取资源,在 Chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 Size 显示from disk cache 或 from memory cache。
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
3.1.1 Expires
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 后过期,需要再次请求。如果修改当前的时间查过Expires给定的时间,那么浏览器就会重新发起请求,进入协商缓存阶段。
3.1.2 Cache-Control
在 HTTP/1.1 中,Cache-Control 是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300
时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
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秒后失效,一般响应头中会包含 Date 字段。
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 标示了客户端不愿意接受新鲜度不多于当前时间加上 min-fresh 设定的时间之和的响应。简单来说不要太新的资源。
从图中我们可以看到,我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
Cache-Control字段属性解读
- 可被复用指的是资源能否被缓存,不能缓存的标识是
no-store
。 - 资源在使用时是否保持一致指的是是否需要每次都检查资源有效性。
no-cache
的设计很容易让人误解,no-cache
指的是每次使用缓存资源是否都需要向服务器验证是否过期,如果不过期则返回 304 和空响应体表示直接使用缓存,过期则重新返回 200 和最新资源。 - 在网络请求中,可能会经过代理服务器,
public, private
控制是否允许代理服务器缓存资源,public
表示均允许缓存,private
表示仅有客户端才允许缓存。 - 权限设置完成后,便是指定缓存的时效。代理服务器缓存有属性
s-maxage
属性,客户端缓存有属性max-age
和缓存字段Expires
控制时效。Cache-Control字段的优先级高于Expires字段,Cache-Control字段中的属性s-maxage
优先级高于max-age
。注意区分属性的优先级和字段的优先级。 - 最大过期时间设置后,还可以设置允许的最大过期时间
max-state
和允许的最小新鲜时间min-fresh
。
3.1.3 Expires 和 Cache-Control
其实这两者差别不大,区别就在于 Expires 是 HTTP1.0 的产物,Cache-Control是HTTP1.1 的产物,两者同时存在的话,Cache-Control 优先级高于 Expires;在某些不支持 HTTP1.1 的环境下,Expires 就会发挥用处。所以 Expires 其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断缓存是否有效的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新。Expires、Cache-Control 字段依赖于本地时间 + 过期时间 + 允许最大过期时间。
如果设置max-age=60s,请求完成后的 60s 内服务端修改了资源,那么浏览器获得的资源是什么呢?浏览器在 60s 内不会重新请求资源,这就导致浏览器获得的资源不是最新的。
强缓存的判断策略可能会导致加载文件不是服务器端最新的内容,是否还记得在 Cache-Control 中,具有一个属性 no-cache
,表示跳过强缓存阶段,每次使用资源都需要发送请求和服务器确认是否为最新资源。可以认为no-cache
正是解决因为依赖于时间的判断没有极高靠性问题而出现的。
那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
3.2 协商缓存
协商缓存就是在 缓存存在 且 缓存已过期 的情况下,浏览器(重新)发送网络请求请求最新的资源的过程。特殊的,当缓存存在,强缓存中的一个属性 Cache-Control:no-cache
可以让强缓存失效并进入协商缓存。
协商缓存有两种情况
- 服务器返回 304 和 Not Modified,协商缓存生效
- 服务器返回 200 和 最新资源,协商缓存失效
条件:是否还记得进入协商缓存的要求?缓存存在 且 缓存已过期 是进入协商缓存的必要条件,特殊的,当缓存存在,强缓存中的一个属性 Cache-Control:no-cache
可以让强缓存失效并进入协商缓存。强缓存中通过 Expires 或 Cache-Control: max-age 判断缓存是否过期,那么在协商缓存中,如何确定是否协商生效呢?或者说服务器如何确定是否返回资源。
这个确定的方法很简单,协商缓存可以通过设置两种响应 HTTP Header 实现,Last-Modified 和 ETag 。
3.2.1 Last-Modified
Last-Modified 字段是当前资源缓存不存在,请求成功后响应头中携带的参数字段,而后被浏览器缓存。Last-Modified 的值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header 信息。
注意:缓存不存在能够表示两种情况:第一次请求、请求后被删除
1 | Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT |
浏览器下一次请求这个资源,浏览器检测到当前资源缓存中有 Last-Modified 这个 header 字段,于是在请求头中添加 If-Modified-Since 这个 header 字段,值就是 缓存的 header 信息中 Last-Modified 字段的值,服务器收到这个资源请求,会根据 If-Modified-Since 的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回 304 和空的响应体,表示浏览器直接从缓存读取资源,此时表示协商缓存生效;如果 If-Modified-Since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回 200 和新的资源文件,此时表示协商缓存失效。返回 304 能够有效的降低数据的传输量,这也是为什么缓存能够提高性能的原因。
简单总结:Last-Modified 和 If-Modified-Since 是配对的,Last-Modified 是缓存不存在时,发送网络请求且请求成功后携带的响应头的字段,而后被浏览器缓存,当浏览器再一次请求这个资源,浏览器检查缓存并发现 Last-Modified 字段,就会在请求头上添加 If-Modified-Since 字段,值就是 Last-Modified 的值。服务器收到请求后对比 if-Modified-Since 和 服务器中改资源的最后修改时间,如果相等返回 304 和空响应体,响应头中还会有 Last-Modified 字段,此时表示协商缓存生效,如果不相等则会返回 200 和最新资源,响应头中携带最新的最后修改时间即 Last-Modified 字段,此时表示协商缓存失效,浏览器缓存当前资源和 header 信息。
Last-Modified 具有一些弊端
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以 HTTP1.1 出现了 ETag
和 If-None-Match
。
3.2.2 ETag
ETag 和 Last-Modified 类似,是在缓存不存在时,浏览器发起请求,请求成功后响应头携带的字段,而后被浏览器缓存。与 Last-Modified 不同的是 ETag 的值是根据某一算法如哈希生成的文件唯一标识,只要资源有变化,ETag就会重新生成 。
浏览器在下一次加载该资源向服务器发送请求时,浏览器检查到当前资源的缓存具有 ETag,则会将缓存中的 ETag 值放到请求头中的 If-None-Match,服务器只需要比较客户端传来的 If-None-Match 和服务器上该资源的 ETag 是否一致,就能判断资源相对客户端是否被修改过。如果服务器发现 ETag 匹配不上,那么直接以常规 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端,此时表示协商缓存失效,浏览器缓存新的资源和 header 信息;如果 ETag 是一致的,则直接返回 304 和空响应体知会客户端直接使用本地缓存即可,此时响应头中还是携带有 ETag。
ETag 和 If-Node-Match 是配对的,与 Last-Modified 和 If-Modifed-Since 一样,但是 ETag 是使用的是文件的唯一标识,能够提升可靠性,但是因为需要计算文件的唯一标识,所以性能会比Last-Modified低。
3.2.3 Last-Modified 和 ETag
在缓存不存在时,浏览器发起网络请求请求资源,请求成功后,浏览器缓存资源文件和缓存 header 信息,在协商缓存阶段检查缓存的 header 信息,如果是 ETag 则给请求投添加 If-None-Match,如果是 Last-Modified,则给请求头添加 If-Modified-Since,服务器对字段做检查比较,决定协商缓存是否生效。注意:ETag 优先级高于 Last-Modified,也就是说会优先使用 ETag。
简单总结
- 在精确度上,ETag 要优于 Last-Modified。
Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么它们的Last-Modified 其实并没有体现出来修改,但是 ETag 每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的 Last-Modified 也有可能不一致。
- 在性能上,ETag 要逊于 Last-Modified,因为 Last-Modified 只需要记录时间,而 ETag 需要服务器通过算法来计算出一个 hash 值。
- 在优先级上,服务器校验优先考虑 ETag
4. 缓存机制
强缓存优先于协商缓存,若强制缓存 ( Expires 和 Cache-Control ) 生效则直接使用缓存,若不生效则进行协商缓存 (Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,浏览器将缓存资源和 header 信息,此时表示协商缓存失效,若协商缓存生效则返回 304 和空响应体,表示继续使用缓存,此时响应头中仍然携带缓存控制字段, 此时表示协商缓存成功。
协商缓存只有在 缓存存在 且 缓存已过期 时才允许进入,特殊的,当缓存存在时,强制缓存(强缓存)的一个字段 Cache-Control:no-cache
可以强制缓存失效并进入协商缓存阶段,其原因就是 Cache-Control:no-cache
字段信息允许不检查强制缓存。
如果没有指定缓存策略,缓存的字段信息,那么浏览器会如何做呢?
对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
5. 实际场景应用缓存策略
5.1 频繁变动的资源
1 | Cache-Control: no-cache |
频繁变动的资源对时效性要求比较强,从直观上,可以不使用缓存字段,但是不使用缓存字段就意味着默认缓存策略即浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间,这不符合要求。
最理想的策略是:每当有该资源的请求时,就和服务器确认资源是否发生了变化,如果发生了变化则重新传送数据,如果没有发生变化则使用本地的缓存数据。
解析:按照理想策略,应该请服务器来鉴定资源有效性。每一个请求都会进入强缓存阶段,所以需要跳出强缓存阶段,接着才能请服务器鉴定。而使强缓存失效的方法有两种,资源不存在,资源已过期。可以设置Cache-Control: max-age=0
,每一次请求都会发现资源过期,强缓存失效并进入协商缓存,携带 ETag 或 Last-Modified 并进行请求,服务器对比值决定协商缓存是否生效。之前也声明过,特殊的,在缓存存在时,强缓存的Cache-Control: no-cache
可以使强缓存失效并进入协商缓存,即跳过强缓存的检查(会进入强缓存阶段,但跳过对比检查)进入协商缓存,刚好符合频繁变动的缓存需求,所以这两种方式都可以,只不过设置过期时间方式浏览器会多做一步过期的检查。
5.2 不常变化的资源
1 | Cache-Control: max-age=31536000 |
通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000
(一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js
, lodash.min.js
等) 均采用这个模式。
6. 用户行为对缓存的影响
所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:
- 打开网页,地址栏输入地址, 查找 Disk Cache 中是否有匹配。如有则使用,如没有则发送网络请求。
- 普通刷新 (F5):因为 TAB 并没有关闭,因此 Memory Cache 是可用的,会被优先使用(如果匹配的话)。其次才是 Disk Cache。
- 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
),服务器直接返回 200 和最新内容。
需要分清楚请求头中的 no-cache 和响应头中的 no-cache,两者的作用是不相同的。
请求头的 no-cache 表示浏览器不想读缓存,并不是说没有缓存。一般在浏览器按 Ctrl+F5 强制刷新时,请求头里就有这个no-cache,也就是跳过强缓存和协商缓存阶段,直接请求服务器。(如果直接按F5的话,请求头是 max-age=0,只跳过强缓存,但进行协商缓存)。
响应头的 no-cache 表示跳过强缓存检查(会进入强缓存阶段,但不检查时间,使强缓存失效并接入协商缓存,max-age=0 检查时间,使强缓存失效,再进入协商缓存)
个人总结
缓存能够有效的减少网络请求数量或降低网络传送的数据量,主要用于页面的性能优化、降低数据流量成本等。
认识学习缓存需要从缓存的位置、缓存的策略和实际场景应用缓存策略三个方面入手。了解缓存位置可以知晓浏览器将缓存放在哪,各种不同的缓存位置有什么优势;了解基础缓存策略(强缓存、协商缓存可以看成是本地判断和远程判断)可以知晓缓存优先级需要控制在哪,是强缓存还是协商缓存。基础缓存策略配合缓存位置,就可以组合出针对不同场景地各种复杂的缓存方式。