理解浏览器缓存,这一篇文章就够了

305 阅读10分钟

前言

关于浏览器缓存,是前端攻城狮必备的知识,面试时出现的频率也是很高,它也是性能优化中简单高效的优化方式。所有我们很有必要去了解并且掌握它,下面就随我来一起学习吧。

缓存

缓存是指可以进行高速数据交换的存储器,它先于内存与CPU交换数据,因此速率很快。 这是百度百科对缓存的解释。我认为,所谓浏览器缓存也可以借用它的说法,即改为先于服务器与客户端交换数据(不发送请求直接使用缓存也算是先于服务器了吧),当然,速率也是很快的。这也使它成为了一种简单高效的性能优化方式。也就是说浏览器会将客户端从服务器拿到的资源先保存一份副本到特定的位置,当浏览器再需要资源的时候,根据特定的缓存策略,就有机会不用跑大老远去找服务器要了,哎,我身边就有,拿来就用,这可真轻松。结果是轻松的,过程却没那么轻松。资源缓存在哪?资源是否允许缓存?是否一直用缓存的资源?下面我们就来探讨探讨缓存位置和缓存策略。

缓存位置

我们打开chrome的开发者工具查看Network,在Size项可以看到有(disk cache)、(memory cache)和直接显示几KB的大小几种字段,这说明这些资源有的来自缓存有的来自服务器

缓存位置有以下几种(按优先级顺序)

  • Service Worker
  • Memory cache
  • Disk cache
  • Push cache

Service Worker

Service Worker是日益流行的PWA技术的核心功能之一,它本质上充当一个介于客户端和服务器之间的代理服务器。利用Service Worker可以拦截网络请求并根据网络状况作出不同的缓存策略,不同于浏览器内部的memory cache和Disk cache,Service Worker给予了我们更大的自由,让我们可以自由控制缓存的文件、缓存的匹配和读取规则,而且缓存是持久性的,即使tab页面或浏览器被关闭,除非手动调用API或者因容量超过限制被浏览器清空。

如果Service Worker没有命中缓存,我们需要调用fetch()方法继续获取资源,此时浏览器会去memory cache或Disk cache查找,也就是根据这个优先级查找资源。但是只要是调用这个函数之后获取到的内容,不管是从其他缓存中获取的还是请求中获取的,浏览器都会显示是从Service Worker中获取的。

另外要注意,出于安全考虑,Service Worker只能被使用在https或本地localhost环境下,因为修改网络请求的能力暴露给中间人攻击会非常危险。

想要进一步了解Service Worker可以点击这里

Memory cache

Memory cache是内存缓存,读取内存缓存肯定比读取硬盘缓存(disk cache)要快,几乎所有资源(主要是preloader一边解析js/css文件,一边网络请求下一个资源、preload预加载资源、当前页面已抓取到的资源)都能缓存入Memory cache,但是内存缓存的空间是有限的,不能把所有资源都存入内存缓存中。随着tab页面的关闭,此次浏览的memory cache会失效。有些时候甚至不会等tab页面的关闭(此次浏览占用的内存太多了),前面的缓存就会随着后面缓存的进入而失效。所以Memory cache只是个短暂的存储。

Memory cache在存储时如果有多个相同的请求都只会被最多请求一次,并且不关心返回资源HTTP的请求头Cache-Control的值,内存缓存的控制权在浏览器,前后端都不能控制。在匹配资源时也并非知识对URL做匹配,还可能会校验Content-Type,CORS等其他特征。

Disk cache

Disk cache又叫HTTP cache,也就是硬盘缓存,虽然读取硬盘缓存比读取内存缓存要慢(起码比去服务器请求资源快得多),但是空间是远远大于内存缓存的,而且是永久性的实际存在于文件系统中。绝大部分的缓存都是来自disk cache。它还允许相同的资源进行跨会话、跨站点使用。

Disk cache会严格遵守缓存策略(也就是我们接下来要讨论的缓存策略),根据HTTP header中的字段来判断资源是否允许缓存、使用和是否过期,并作出相应的动作。

Push cache

Push cache是HTTP/2中的产物,因为HTTP/2还不是很流行,这里只简单介绍一下。Push cache是和HTTP连接紧密相连的,每个HTTP连接都有自己的Push cache,因为多个页面可以使用同一个HTTP连接,所以意味着多个页面可以使用同一个Push cache。Push cache中缓存的生命周期也是由HTTP连接决定的,也就是说Push cache仅在连接关闭前有效,所以可能会出现server push的资源到达之前就关闭了连接,这样就需要重新建立连接,带来了不必要的麻烦,所以最好用Push cache存储一些立即使用的资源。最后,这些资源只能被使用一次,当浏览器从Push cache中取出资源之后,Push cache会将其删除。

缓存策略

缓存策略是基于Disk cache来讨论的,主要分为两种:

  • 强缓存
  • 协商缓存

强缓存

强缓存,我个人理解为”强硬的缓存“,即只要缓存没过期,就必须用缓存,它依赖HTTP header的Expire和Cache-Control这两个字段。

Expire是HTTP/1.0的残留字段,用来指定资源的到期时间,此时间为服务器端的具体的时间点,在此时间前都不需要向服务器请求资源,直接使用缓存中的资源。但是它存在缺陷,即受限于本地时间,如果修改本地时间,可能会造成缓存失效。于是在HTTP/1.1中出现了Cache-Control字段。

Cache-Control是HTTP/1.1中最重要的规则,主要用于控制缓存,其在头部设置的主要指令有:

  • public:可向任意方提供缓存,即客户端和代理服务器都可以进行缓存;【响应指令】
  • private:仅特定用户允许缓存,即只允许客户端缓存;【响应指令】
  • no-cache:缓存前必须先确认其有效性,即可以缓存,但是使用缓存之前必须向服务器确认一下数据是否已改变(协商缓存);【请求指令、响应指令】
  • no-store:不缓存请求或响应的任何内容;即不作缓存【请求指令、响应指令】
  • max-age=[ 秒]:响应的最大Age值,即缓存的过期时间,例如max-age=50 为50秒之后缓存过期【请求指令、响应指令】
  • s-maxage=[ 秒]:公共缓存服务器响应的最大Age值,和max-age一样只不过是在代理服务器中生效,s-maxage的优先级高于max-age,如果存在s-maxage会覆盖掉max-age和Expire【响应指令】
  • max-stale( =[ 秒]):接收已过期的响应;即容忍过期多少秒的时间,不加秒的话,为可接受过期任意时间的资源;【请求指令】
  • min-fresh=[ 秒]:期望在指定时间内的响应仍有效;即希望拿到当前时间加上设定的秒数还没过期的缓存,例min-fresh=50 为现在时间加上50秒如果超过了缓存的过期时间就不要了【请求指令】

HTTP/1.1通过设置Cache-Control的max-age指令的参数来设置缓存的过期时间,max-age的优先级高于Expire,但是为了兼容HTTP/1.0,实际项目中应将两个字段全设置。

协商缓存

当强缓存超过了过期时间,或者将Cache-Control设置为no-cache,那么就会进行协商缓存。协商缓存,顾名思义需要先商量商量再进行缓存。协商缓存主要依赖Cache-Control的Last-Modified与If-Modified-Since、Etag与If-None-Match这两组指令。

浏览器在第一次访问资源时,服务器会在返回资源的Header添加Last-Modified指令

  • Last-Modified:资源的最后修改时间

客户端拿到资源后会缓存资源(包括Header),当浏览器下一次请求资源的时候,会把缓存的Last-Modified作为请求头部的If-Modified-Since指令的参数传给服务器

  • If-Modified-Since:比较资源更新时间

服务器拿到If-Modified-Since后,会与服务器中相应资源的最后修改时间作对比,如果相同,则返回304 Not Modified状态码,即提示客户端资源没有改变,可以使用缓存;如果不同,则返回200 OK状态码,并携带更改后的资源,客户端拿到资源后,会重新缓存资源。

但是Last-Modified是存在缺陷的

  • Last-Modified最低是以秒为单位的,如果一秒内修改完文件,服务器依旧会认为资源还是命中了,不会返回正确的资源
  • 如果在本地打开缓存文件,即使不更改内容,Last-Modified也会被修改,导致服务器不能命中缓存并发送相同的资源
  • 如果文件是服务器动态生成的,那么该文件的Last-Modified永远是文件的更新时间。尽管文件可能没有变化,所以起不到缓存的作用

鉴于Last-Modified存在缺陷,HTTP/1.1添加了Etag和If-None-Match两个指令。

  • Etag:资源的匹配信息,它是一种可将资源以字符串形式做唯一的标识方式。服务器会为每份资源分配对应的Etag值。当资源更新时,Etag值也需要更新。生成ETag值时,并没有统一的算法规则,而仅仅是由服务器来分配。且ETag分强ETag值和弱ETag值之分。强ETag值,不论实体发生多么细微的变化都会改变其值。弱ETag值只用于提示资源是否相同,弱ETag值会在字段值最开始处附加W/。

客户端访问资源时,服务器会在返回的资源的Header添加ETag指令,客户端收到响应后会缓存资源和响应的ETag值,当再次访问服务器时会将资源的ETag值作为请求Header中的If-None-Match指令的参数。

  • If-None-Match:比较实体标记(ETag)

服务器拿到If-None-Match后,会与服务器中资源的ETag进行比较,如果相同,返回304 Not Modified状态码,资源没有更改可以使用缓存,如果不同,则会返回200 OK状态吗,并在请求体中携带更改后的资源,客户端拿到后会重新换缓存资源和ETag值。

小结

本篇文章关于浏览器缓存的知识总结的已经比较全面了,希望能给你们带来帮助,如果在阅读的过程中发现错误或不当请尽管指出,如果觉得写的还不错、真的学到了知识,可以赏个小赞,让我们一起努力,加油!!!