前端面试查漏补缺--(六) 浏览器缓存

11,042 阅读16分钟

前言

本系列最开始是为了自己面试准备的.后来发现整理越来越多,差不多有十二万字符,最后决定还是分享出来给大家.

为了分享整理出来,花费了自己大量的时间,起码是只自己用的三倍时间.如果喜欢的话,欢迎收藏,关注我!谢谢!

文章链接

合集篇:

前端面试查漏补缺--Index篇(12万字符合集) 包含目前已写好的系列其他十几篇文章.后续新增值文章不会再在每篇添加链接,强烈建议议点赞,关注合集篇!!!!,谢谢!~

浏览器缓存概述

什么是缓存

缓存(和我之前文章所说的前端存储是不一样的!注意区分):
是一种保存资源副本并在下次请求时直接使用该副本的技术。那么浏览器缓存就是浏览器请求网站留下的资源副本。

当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。

缓存的好处

  • 缓解服务器压力(不用每次去请求资源);
  • 提升性能(打开本地资源速度当然比请求回来再打开要快得多);
  • 减少带宽消耗;

缓存的分类

缓存在宏观上可以分成两类:

  • 私有缓存: 只能用于单独用户,最常见的就是浏览器缓存,也是本片重点讲解的.
  • 共享缓存: 够被多个用户使用的缓存,也就是那些能被各级代理的缓存.

浏览器的缓存策略

浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。

根据响应头,浏览器缓存策略一般分为三种:强缓存,协商缓存启发式缓存

浏览器常见字段和指令

在讲强缓存和协商缓存之前先提前了解以下这几个字段和指令,便于后面理解:

  1. expires: 告知客户端资源缓存失效的绝对时间
  2. last-modified: 资源最后一次修改的时间
  3. Etag: 文件的特殊标识
  4. cache-control:告诉客户端或是服务器如何处理缓存。
  5. private: cache-control里的响应指令.表示客户端可以缓存
  6. public: cache-control里的响应指令.表示客户端和代理服务器都可缓存.如果没有明确指定private,则默认为public。
  7. no-cache: cache-control里的指令.表示需要可以缓存,但每次用应该去向服务器验证缓存是否可用
  8. no-store: cache-control字段里的指令.表示所有内容都不会缓存,强制缓存,对比缓存都不会触发.
  9. max-age=xxx: cache-control字段里的指令.表示缓存的内容将在 xxx 秒后失效

强缓存

强缓存简单理解就是:给浏览器缓存设置过期时间,超过这个时间之后缓存就是过期,浏览器需要重新请求。

强缓存主要是通过http请求头中的Cache-Control和Expires两个字段控制

expires

expires是一个HTTP/1.0的字段,它给浏览器设置了一个绝对时间,当浏览器时间超过这个绝对时间之后,重新向服务器发送请求。

用法:

它描述的是一个绝对时间,用GMT格式的字符串表示

Expires: Wed Feb 20 2019 11:25:41 GMT

也可以在html文件里直接使用:

<meta http-equiv="expires" content="Wed Feb 20 2019 11:25:41 GMT">

弊端:

  • Expires返回的是服务器的时间,但判断的时候用的却是客户端的时间,这就导致Expires很被动,因为用户有可能改变客户端的时间,导致缓存时间判断出错,这也是引入Cache-Control:max-age指令的原因之一。

cache-control: max-age

为了解决expires存在的问题,Http1.1版本中提出了cache-control:max-age,该字段与expires的缓存思路相同,都是设置了一个过期时间,不同的是max-age设置的是相对缓存时间开始往后的多少秒,因此不再受日期不准确情况的影响。

优先级:
在优先级上:max-age>Expires。当两者同时出现在响应头时,Expires将被max-age覆盖.

用法:

Cache-control: max-age=666

表示资源会在 666 秒后过期,需要再次请求。

强缓存在浏览器上的表现

  • Firefox浏览器对强缓存表现为一个灰色的200状态码。
  • Chrome浏览器状态码表现为:200 (from disk cache)或是200 OK (from memory cache)

说明:Chrome会根据本地内存的使用率来决定缓存存放在哪,如果内存使用率很高,放在磁盘里面,磁盘的使用率很高会暂时放在内存里面。这就可以比较合理的解释了为什么同一个资源有时是from memory cache有时是from disk cache的问题了。

但是强制缓存存在一个问题,该缓存方式优先级高,如果在过期时间内缓存的资源在服务器上更新了,客服端不能及时获取最新的资源。这时怎么办?于是就有了协商缓存.

协商缓存

协商缓存解决了无法及时获取更新资源的问题。它利用下面会讲到的两组字段,对资源做标识.然后由服务器做分析,如果资源未更新,则返回304状态码.那么浏览器则会从缓存中读取资源,否则重新请求资源。

协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的。

Last-Modified与If-Modified-Since

  • 1,浏览器第一次向服务器请求资源,服务器会在返回这个资源的同时,在response的header加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间:Last-Modified: Wed Feb 20 2019 14:08:32 GMT

  • 2,浏览器之后再向服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值:Last-Modified: Wed Feb 20 2019 14:08:32 GMT

  • 3,服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,返回200,就正常返回资源内容。

    • 当服务器返回304 Not Modified的响应时,response的header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304时的response header.
  • 4,浏览器收到304的响应后,就会从缓存中加载资源。

  • 5,浏览器收到200的响应后,则从服务器加载新资源时,Last-Modified Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值。

弊端:
【Last-Modified,If-Modified-Since】都是根据服务器时间返回的header,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header配合起来管理协商缓存是非常可靠的,但是它们是以秒为单位进行更新,如果小于该单位高频进行更新的话,则不适合采用该方法。 这时候协商缓就不那么的可靠了。所以就有了另外一对header来管理协商缓存,这对header就是【ETag、If-None-Match】。

ETag与If-None-Match

  • 1,浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串ETag: shotcat-66666只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充Last-Modified的问题.
  • 2,浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值If-None-Match: shotcat-66666.
  • 3,服务器再次收到资源请求时,根据浏览器传过来If-None-Match和然后再根据资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,则返回200,并正常返回资源内容。与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化
  • 4,浏览器收到304的响应后,就会从缓存中加载资源。
  • 5,浏览器收到200的响应后,则从服务器加载新资源时,ETag在重新加载的时候会被更新,下次请求时,If-None-Match会启用上次返回的ETag值。

Etag和Last-Modified非常相似,都是用来判断一个参数,从而决定是否启用缓存。但是ETag相对于Last-Modified也有其优势,可以更加准确的判断文件内容是否被修改, 从而在实际操作中实用程度也更高,但缺点也很明显,由于需要对资源进行生成标识,性能方面就势必有所牺牲

优先级:
ETag与If-None-Match > Last-Modified与If-Modified-Since, 同时存在时, 前者覆盖后者.

启发式缓存

我跟我们的请求头中确定缓存过期时间的字段一个都没有.例如:

Age:23146
Cache-Control: public
Date:Tue, 28 Nov 2017 12:26:41 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding

此时则会默认触发浏览器启发式缓存:
浏览器会根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。

缓存的优先级

在缓存策略上:强缓存>协商缓存>启发式缓存

进一步分析可得出,以下优先级:
Cache-Control > Expires > ETag > Last-Modified

非常注意

其他所有教程都是告诉你浏览器肯定是先检查强缓存,再检查协商缓存!但实际它们都忽略了一点.其实浏览器是先检查Cache-Control,如果为no-store.则浏览器 所有内容都不会缓存,强制缓存,协商缓存统统都不会触发!!!

补充:Pragma

它是HTTP/1.0里面的一个字段,在http1.1已被抛弃,使用Cache-Control替代.但为了做http协议的向下兼容,很多网站依旧会带上这个字段但优先级很高.

测试发现,Chrome和Firefox中Pragma的优先级高于Cache-Control和Expires.

一般可能会这么用:<meta http-equiv="Pragma" content="no-cache">

服务端响应添加'Pragma': 'no-cache',浏览器表现行为和强制刷新类似。

补充: Cache-Control

通过cache-control的指令可以控制告诉客户端或是服务器如何处理缓存。这也是11个字段中指令最多的一个,也是经常被用到的.

请求指令

指令参数说明
no-cache强制源服务器再次验证
no-store不缓存请求或是响应的任何内容
max-age=[秒]缓存时长,单位是秒缓存的时长,也是响应的最大的Age值
min-fresh=[秒]必需期望在指定时间内响应仍然有效
no-transform代理不可更改媒体类型
only-if-cached从缓存获取
cache-extension-新的指令标记(token)

响应指令

指令参数说明
public任意一方都能缓存该资源(客户端、代理服务器等)
private可省略只能特定用户缓存该资源
no-cache可省略缓存前必须先确认其有效性
no-store不缓存请求或响应的任何内容
no-transform代理不可更改媒体类型
must-revalidate可缓存但必须再向源服务器进确认
proxy-revalidate要求中间缓存服务器对缓存的响应有效性再进行确认
max-age=[秒]缓存时长,单位是秒缓存的时长,也是响应的最大的Age值
s-maxage=[秒]必需公共缓存服务器响应的最大Age值
cache-extension-新指令标记(token

请注意no-cache指令很多人误以为是不缓存,这是不准确的,no-cache的意思是可以缓存,但每次用应该去想服务器验证缓存是否可用。no-store才是不缓存内容。 另外部分指令也可以组合使用,比如:

Cache-Control: max-age=100, must-revalidate, public
复制代码

上面指令的意思是缓存的有效时间为100秒,之后访问需要向源服务器发送请求验证,此缓存可被代理服务器和客户端缓存。

浏览器缓存判断流程

HTTP中和缓存相关的首部字段

HTTP报文,主要由以下两部分构成:

  1. 首部(header):包含了很多字段,比如:cookie、缓存、报文大小、报文格式等等);
  2. 主体(body):HTTP请求真正要传输的部分,比如:一个HTML文档,一个js文件;

以上我们知道浏览器对于缓存的处理过程,也简单的提到了几个相关的字段。🤧接下来我们具体看下这几个字段:

1. 通用首部字段

字段名称说明
Cache-Control控制缓存具体的行为
PragmaHTTP1.0时的遗留字段,当值为"no-cache"时强制验证缓存
Date创建报文的日期时间(启发式缓存阶段会用到这个字段)

2. 响应首部字段

字段名称说明
ETag服务器生成资源的唯一标识
Vary代理服务器缓存的管理信息
Age资源在缓存代理中存贮的时长(取决于max-age和s-maxage的大小)

3. 请求首部字段

字段名称说明
If-Match条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改
If-None-Match和If-Match作用相反,服务器根据这个字段判断文件是否有新的修改
If-Modified-Since比较资源前后两次访问最后的修改时间是否一致
If-Unmodified-Since比较资源前后两次访问最后的修改时间是否一致

4. 实体首部字段

字段名称说明
Expires告知客户端资源缓存失效的绝对时间
Last-Modified资源最后一次修改的时间

用户操作行为对缓存的影响

操作说明
打开新窗口如果指定cache-control的值为private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。而如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,例如:Cache-control: max-age=5 表示当访问此网页后的5秒内不会去再次访问服务器.
在地址栏回车如果值为private或must-revalidate,则只有第一次访问时会访问服务器,以后就不再访问。如果值为no-cache,那么每次都会访问。如果值为max-age,则在过期之前不会重复访问。
按后退按扭如果值为private、must-revalidate、max-age,则不会重访问,而如果为no-cache,则每次都重复访问.
按刷新按扭无论为何值,都会重复访问.(可能返回状态码:200、304,这个不同浏览器处理是不一样的,FireFox正常,Chrome则会启用缓存(200 from cache))
按强制刷新按钮当做首次进入重新请求(返回状态码200)

如果想在浏览器点击“刷新”按钮的时候不让浏览器去发新的验证请求呢?办法找到一个,知乎上面一个回答,在页面加载完毕后通过脚本动态地添加资源:

$(window).load(function() {
  	var bg='http://img.infinitynewtab.com/wallpaper/100.jpg';
  	setTimeout(function() {
    	$('#bgOut').css('background-image', 'url('+bg+')');
  	},0);
});

选择合适的缓存策略

对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略

  • 对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存
  • 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。
  • 对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。

感谢及参考

求内推

如果有好的前端岗位,欢迎内推我,谢谢。

本科,三年运维,五年前端。 base 武汉 邮箱 bupabuku@foxmail.com