最近面试被问到了
HTTP缓存
的内容,没有了解过太多,只是在某些文章中看到过,知道强缓存
和协商缓存
这俩名词,具体是什么并没有了解过。 关于前端的缓存其实大致就可以区分为浏览器缓存和HTTP缓存
,平时可能在业务开发中用到浏览器缓存
会比较多,今天学习一下HTTP缓存
的内容
啥是HTTP缓存
HTTP缓存
指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有要请求资源
的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
常见的HTTP缓存
只能缓存GET
请求响应的资源,对于其它类型的响应则无能为力,所以后续说的请求缓存都是指GET
请求。
HTTP缓存
都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在response header
头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存直接200,否则就把请求参数加到request header
头中传给服务器,看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源。
HTTP缓存的分类
根据是否需要重新向服务器发起请求来分类,可分为(强缓存, 协商缓存
)根据是否可以被单个或者多个用户使用来分类,可分为(私有缓存,共享缓存
)强缓存如何生效,不需要再和服务器发生交互,而协商缓存不管是否生效,都需要与服务端发生交互。下面是强缓存和协商缓存的一些对比:
强缓存 | 协商缓存 | |
---|---|---|
缓存存放位置 | 本地浏览器 | 本地浏览器 |
HTTP状态码 | 200 | 304 |
谁来决定 | Pragma Cache-Control Expires | ETag/If-Not-Match Last-Modified/If-Modified- |
操作是否有效 | 1、Ctrl + F5强制刷新 ---无效 2、F5刷新 -----------------无效 3、 地址栏回车 -----------有效 4、 页面链接跳转 --------有效 5、新开窗口 ---------------有效 6、前进、后退 ------------有效 | 1、Ctrl + F5强制刷新 ---无效 2、F5刷新 -----------------有效 3、 地址栏回车 -----------有效 4、 页面链接跳转 --------有效 5、新开窗口 --------------有效 6、前进、后退 -----------有效 |
强缓存
强制缓存在缓存数据未失效的情况下(即CaChe-Control
的max-age
没有过期或者Expires
的缓存时间没有过期),那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。强制缓存生效时,HTTP
状态码为200。这种方式页面的加载速度是最快的,性能也不是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了。这种情况就是我们在开发中经常遇到的,比如你修改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强缓存,所以Ctrl + F5
一顿操作之后就好了。跟强制缓存相关的header
头属性有(Pragma/Cache-Control/Expires
)
header属性 | 可选值 | 优先级 | 优缺点 |
---|---|---|---|
Pragma(HTTP/1.0) | no-cache: 不直接使用缓存,根据新鲜度来使用缓存 | 高 | 1、响应头不支持这个属性 2、为了兼容HTTP/1.0的客户端 3、 在HTTP1.1中已被废弃 |
Cache-Control(HTTP/1.1) | 1、no-cache: 不直接使用缓存,根据新鲜度来使用缓存 2、 no-store: 不使用缓存,每次都是请求下载新资源 3、max-age: xx秒,缓存时长 4、 public/private: 是否只能被单个用户使用,默认为private 5、 must-revalidate: 每次访问需要缓存校验 | 中 | 1、请求头和响应头都支持这个属性 2、不适用于HTTP/1.0 3、在缓存未失效前,获取不到修改后的资源 |
Expires(HTTP/1.0+) | GMT时间 | 低 | 1、服务器和客户端的时间不一致会出问题 2、适用于HTTP/1.0和HTTP1.1 3、在缓存未失效前,获取不到修改后的资源 |
这个Pragma
和Cache-Control
共存时的优先级问题还有点异议,我在不同的文章里发现:有的说Pragma
的优先级更高,有的说Cache-Control
高。为了搞清楚这个问题,我决定动手操作一波,首先我用nodejs
搭建后台服务器,目的是设置缓存参数,具体代码如下:
然后再浏览器上访问:http://localhost:8888
第一次访问时都是从后台返回的数据:
第二次访问时:
最终得出结论:
Pragma
和Cache-control
共存时,Pragma
的优先级是比Cache-Control
高的。
注意:
在chrome
浏览器中返回的200状态会有两种情况:
1、from memory cache
(从内存中获取/一般缓存更新频率较高的js、图片、字体等资源)
2、from disk cache
(从磁盘中获取/一般缓存更新频率较低的js、css等资源)
这两种情况是chrome
自身的一种缓存策略,这也是为什么chrome
浏览器响应的快的原因。其他浏览返回的是已缓存状态,没有标识是从哪获取的缓存。
chrome
浏览器:
Firefox
浏览器:
协商缓存
当第一次请求时服务器返回的响应头中没有Cache-Control
和Expires
或者Cache-Control
和Expires
过期还或者它的属性设置为no-cache
时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务端对比判断资源是否进行了修改更新。如果服务端的资源没有修改,那么就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码,服务区就会返回更新后的资源并且将缓存信息一起返回。跟协商缓存相关的header
头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since
) 请求头和响应头需要成对出现。
header属性 | 可选值 | 优先级 | 优缺点 |
---|---|---|---|
ETag/If-Not-Match(HTTP/1.1) | 校验值 | 高 | 1、默认使用hash算法,在分布式环境下可能会出现不同服务器生成的ETag值不一致 2、精确的判断资源有无被修改,可识别一秒内的修改次数 3、计算ETag需要性能消耗 |
Last-Modified/If-Modified-Since(HTTP/1.0) | GMT时间 | 低 | 1、只要资源修改,无论内容有无变化,都会将资源返回客户端 2、以时刻为标识,无法获取一秒内的修改变化 3、某些服务器不能准确获取最后的修改时间 |
协商缓存的执行流程是这样的:当浏览器第一次向服务器发送请求时,会在响应头中返回协商缓存的头属性:ETag
和Last-Modified
,其中ETag
返回的是一个hash
值,Last-Modified
返回的是GMT
格式的最后修改时间。然后浏览器在第二次发送请求的时候,会在请求头中带上与ETag
对应的If-Not-Match
,其值就是响应头中返回的ETag
的值,Last-Modified
对应的If-Modified-Since
。服务器在接收到两个参数后会做比较,如果返回的是304状态码,则说明请求的资源没有修改,浏览器可以直接在缓存中取数据,否则,服务器会直接返回数据。
注意:
ETag/If-Not-Match
是在HTTP/1.1
出现的,主要是解决以下问题:
- Last-Modified标注的是最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
- 如果某些文件被修改了,但是内容并没有任何变化,而
Last-Modified
却改变了,导致文件没法使用缓存 - 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
私有缓存(浏览器级缓存)
私有缓存只能用于单独的用户:(Cache-Control: Private
)
共享缓存(代理级缓存)
共享缓存可以被多个用户使用: (Cache-Control: Public
)
HTTP缓存有什么用
- 减少了冗余的数据传输,节省了网络传输的资源消耗
- 缓解了服务器的压力,大大提高了网站的性能
- 加快了客户端加载网页的速度
怎么使用HTTP缓存
一般需要缓存的资源有HTML
页面和其它静态资源
HTML页面缓存的设置主要是在标签中嵌入 标签,这种方式只对页面有效,对页面上的资源无效
HTML页面禁用缓存的设置如下
<meta http-equiv="pragma" content="no-cache">
// 仅在IE浏览器才识别的标签,不一定会在请求字段加上Pragma, 但的确会让当前页面每次都发新请求
<meta http-equiv="cache-control" content="no-cache">
// 其它主流浏览器识别的标签
<meta http-equiv="expires" content="0">
// 仅有IE浏览器才识别的标签,该方式仅仅作为指挥IE缓存时间的标识,你并不能在请求或响应报文中找到Expires字段
HTML设置缓存如下
<meta http-equiv="Cache-Control" content="max-age=7200">
// 其它主流浏览器识别的标签
<meta http-equiv="Expires" content="Mon, 20 Aug 2021 23:00:00 GMT">
// 仅有IE浏览器才识别的标签
静态资源的缓存一般是在web服务器上配置的,常用的web服务器有: nginx、apache。具体的配置这里不做详细介绍,大家自行查阅。
不想使用缓存的几种方式
Ctrl + F5
强制刷新,都会直接向服务器提取数据- 按
F5
刷新或浏览器的刷新按钮,默认加上Cache-Control: max-age=0
,即会走协商缓存 - 在IE浏览器下不想使用缓存的做法:打开IE,点击工具栏上的工具 -> Internet选项 -> 常规 -> 浏览器历史记录,设置,选择
从不
,然后保存。最后点击删除
把Internet临时文件都删掉(IE缓存的文件就是Internet临时文件)。 - 还有就是上面讲到的禁用缓存的做法
- 对于其他浏览器也有清楚缓存的办法
HTTP缓存需要注意的几点
- 强缓存情况下,只要缓存还没过期,就会直接从缓存中取数据,就算服务端有数据变化,也不会从服务端获取了,这样就无法获取到修改后的数据。解决的方法有:在修改后的资源加上随机数,确保不会从缓存中取。
例如: www.abc.com/article/a.c… www.abc.com/article/a.2…
- 尽量减少304的请求,因为我们知道,协商缓存每次都会与后台服务器进行交互,所以性能上不是很好。从性能上来看尽量多使用强缓存
- 在
FireFox
浏览器下,使用Cache-Control: no-cache
是不生效的,其识别的是no-store
。这样能达到其他浏览器使用Cache-Control: no-cache
的效果。所以为了兼容FireFox
浏览器,经常会写成Cache-Control: no-cache, no-store
。 - 与缓存相关的几个header属性有:Vary、Date/Age
Vary vary本身是“变化”的意思,而在http报文中更趋于是“vary from”(与。。。不同)的含义,它表示服务端会以什么基准字段来区分、筛选缓存版本。 在服务端有着这么一个地址,如果是IE用户则返回针对IE开发的内容,否则返回另一个主流浏览器版本的内容。 格式:Vary: User-Agent 知会代理服务器需要以 User-Agent 这个请求首部字段来区别缓存版本,防止传递给客户端的缓存不正确。
Date/Age 响应报文中的 Date 和 Age 字段:区分其收到的资源是否命中了代理服务器的缓存。 Date 理所当然是原服务器发送该资源响应报文的时间(GMT格式),如果你发现 Date 的时间与“当前时间”差别较大,或者连续F5刷新发现 Date 的值都没变化,则说明你当前请求是命中了代理服务器的缓存。
Age 也是响应报文中的首部字段,它表示该文件在代理服务器中存在的时间(秒),如文件被修改或替换,Age会重新由0开始累计。
浏览器缓存
下面说说最常用的浏览器缓存有: cookie
、sessionStorage
、localStorage
这三者的主要特征如下:
本地缓存 | 容量 | 特征 |
---|---|---|
cookie | 4KB | 1、兼容各种浏览器 2、每次都会和后台交互 3、可设置过期时间 |
sessionStorage | 5MB | 1、H5新增特性,不兼容低版本浏览器 2、本地存储,不会每次和后台交互 3、会话关闭,缓存失效 |
localStorage | 5MB | 1、H5新增特性,不兼容低版本浏览器 2、本地存储,不会每次和后台交互 3、本地缓存,除非手动删除,否则一直存在 |
总结
1、对于强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行协商缓存策略
2、对于协商缓存,将缓存信息中的ETag
和Last-Modified
通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。