前言

在前端面试中,可能或多或少都会被提及缓存问题,而这个问题大多数都是作为业务中不得不考虑的一个性能优化点,如果平时没有怎么关注或是特意去了解这块的童鞋们,可能就是不太了解其中的原由,那么今天我们就这个缓存问题来细细分析,帮助一些还不是太明白的或是刚入门的前端童鞋们梳理梳理,理解理解,那就话不多说,开始吧^-^。

概述

其实缓存有很多种,包括:HTTP缓存DNS缓存CDN缓存等等。今天主要介绍的就是HTTP缓存,在开始介绍之前,先简单说说HTTP报文。

HTTP报文就是浏览器和服务器之间通信时发送响应的数据块。
浏览器向服务器请求数据,发送请求(request)报文;服务器向浏览器返回数据,返回响应(response)报文。

报文主要包含以下两部分:

  1. 属性的头部(header);
  2. 数据的主体部分(body);

浏览器缓存策略

浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。如果缓存有效,则使用本地缓存;否则,向服务器发起请求并携带缓存标识。根据是否需要向服务器发起HTTP请求,将缓存过程划分为两个部分:

  1. 强制缓存:服务器通知浏览器一个缓存时间,在缓存时间内,下次请求直接使用缓存,不在时间内,执行比较缓存策略;
  2. 协商缓存:让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存复用率,将缓存信息中的EtagLast-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存;

强缓存优先于协商缓存。

缓存运作的整体流程图如下:
流程图

强制缓存

当请求命中强制缓存时,浏览器不会将本次请求发往服务器,而是直接从缓存中读取内容,在Chrome中打开控制台,在network中显示的是memory cache或者是disk cache

强制缓存

强缓存可以通过设置两种HTTP Header实现:Expires(1.0)Cache-Control(1.1)

Expires

Expires是一个绝对时间,是缓存过期时间。用以表达在这个时间点之前发起请求可以直接从浏览器中读取数据,而无需重新发起请求。

Expires = max-age + 到期时间。由于受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

该字段是服务器响应消息头字段,告诉浏览器在过期时间之前可以直接从浏览器缓存中存取数据。由于是绝对时间内,用户可能将本读时间进行修改,从而导致浏览器判断缓存失效,重新请求资源。在不考虑修改,时差或者误差等因素也可能照成客户端于服务端的时间不一致,致使缓存失效。

优点:

  1. HTTP 1.0产物,可以在HTTP 1.0和1.1中使用,简单、易用。
  2. 以时刻标识失效时间。

缺点:

  1. 时间是由服务器发送的,如果服务器时间和客户端时间不一致,可能会出现问题。
  2. 存在版本问题,到期之前的修改客户端是不可知的。

Cache-Control

Cache-Control的优先级比Expires的优先级高。该字段表示资源缓存最大有效时间,在该时间内,客户端不需要向服务器发送请求。前者的出现是为了解决Expires在浏览器中,时间被手动更改导致缓存判断错误的问题。如果同时存在则使用Cache-Control

常见的取值有(完整的列表可以查看MDN):

  • private(默认值):客户端可以缓存,代理服务器不能缓存
  • public:客户端和代理服务器都可缓存
  • no-cache:在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)
  • max-age:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
  • no-store:缓存不应该存储有关客户端请求或服务器响应的任何内容,即使不使用任何缓存

举个栗子🌰:

强制缓存

图中Cache-Control指定了max-agepublic,缓存时间为31536000秒(365天)。
也就是说,在365天内再次请求这条数据,都会直接获取缓存数据库中的数据,直接使用。

优点:

  1. HTTP 1.1产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。
  2. 比Expires多了很多选项设置。

缺点:

  1. 存在版本问题,到期之前的修改客户端是不可知的。

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。而整个过程是需要发出请求的。

协商缓存由2组字段(不是2个),控制协商缓存的字段有:

  • Last-Modified/If-Modified-since(http 1.0): 表示的是服务器的资源最后一次修改的时间;
  • Etag/If-None-match(http 1.1): 表示的是服务器资源的唯一标识,只要资源有变化,Etag就会重新生成;

Etag/If-None-match 的优先级高于Last-Modified/If-Modified-since。

Last-Modified/If-Modified-since

  1. 服务器通过 Last-Modified 字段告知客户端(返回资源的同时在header添加),表示资源最后一次被修改的时间,浏览器将这个值和内容一起记录在缓存数据库中
  2. 下一次请求相同的资源时,浏览器会从自己的缓存中找出“不确定是否过期的”缓存,因此在请求头中将上次的Last-Modified的值写入到请求头的If-Modified-since字段
  3. 服务器会将If-Modified-since的值与Last-Modified字段进行对比。如果相等,这表示未修改,响应304;反之则表示修改了,响应 200 状态码,并返回数据

优点:

  1. 不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200及资源内容。
  2. 如果返回的是 304,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此在响应体的体积上节省是很好的优点

缺点:

  1. 只要资源发生了修改,无论内容是否发生了实质性的改变,都会将该资源返回客户端。例如周期性重写,但这种情况下资源包含的数据实质是一样的。
  2. 以时刻作为标识,无法识别一秒内多次修改的情况。如果资源更新的速度是秒以下的单位,那么该缓存是不能被使用的,因为它的时间最低单位是秒。
  3. 某些服务器不能精确的得到文件最后修改时间。
  4. 如果文件是服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能并没有变化,所以也起不到缓存的作用。

Etag/If-None-match

为了解决上述问题,出现了一组新的字段Etag/In-None-Match

  1. Etag是上一次加载资源时,服务器返回的。它的作用是唯一用来标识资源是否有变化
  2. 浏览器在下一次发起请求时,会将上一次返回的Etag值赋值给If-None-Match并添加在 Request Header 中。服务端匹配传入的值与上次是否一致,如果一致返回304,浏览器则读取本地缓存,否则返回200和更新后的资源及新的Etag

优点:

  1. 可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况
  2. 不存在版本问题,每次请求都会去服务器进行校验

缺点:

  1. 计算Etag值需要性能损耗
  2. 分布式服务器存储情况下下,计算Etag的算法如果不一致,会导致浏览器从一个服务器上获取得页面内容后到另一台服务器上进行验证时出现Etag不匹配的情况

总结

对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。

对于协商缓存,将缓存信息中的EtagLast-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。


提莫找蘑菇
1.9k 声望39 粉丝

不要给我说什么:react/angular/Typescript/vue/es6/es7/babel/webpack....老夫就用Jquery!0.0