缓存从来都是前端的一个痛点,很多前端搞不太清楚缓存到底是何物,从而给自己创造了一些麻烦,
本文参考许多资料归纳总结,希望看完能对您有所帮助。
(一) 前言:
缓存是一种保存资源副本并且在下次请求时直接使用该副本的技术
说实话,我起始真的不知道怎么去介绍缓存,所以引用了上面相对官方的定义。
我想几乎每个开发者都碰到过缓存的问题吧,甚至有很多情况下我们会说这个问题已经修复了,你清理下缓存就好了。
这篇文章我们就细细的来挖掘下缓存的种种轶事。
(二) 🦋缓存种类:
很多开发者习惯把cookie、webStorage以及IndexedDB存储的数据也称之为缓存,理由是都是保存在客户端的数据,没有什么区别。
其实这是不严谨的,cookie的存在更多的是为了让服务端区别用户,webStorage和IndexedDB则更多用在保存具体的数据和在客户端存储大量结构化数据(文件/blobs)上面。
缓存只有一种——它是请求资源的副本
缓存的优势
纳尼?你问我为什么要缓存?😱
那就太容易说道了🤣,缓存好处有很多:
-
缓解服务器压力(不用每次去请求资源);
-
提升性能(打开本地资源速度当然比请求回来再打开要快得多);
-
减少带宽消耗(我相信你可以理解);
宏观分类
缓存在宏观上可以分成两类:私有缓存和共享缓存。
共享缓存就是那些能被各级代理缓存的缓存(咋觉得有点绕)。
共享缓存存储的响应能够被多个用户使用。私有缓存只能用于单独用户。

(私有)浏览器缓存
私有缓存只能用于单独用户。你可能已经见过浏览器设置中的“缓存”选项。
浏览器缓存拥有用户通过 HTTP 下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。
它同样可以提供缓存内容的离线浏览。
(共享)代理缓存
共享缓存可以被多个用户使用。
例如,ISP CDN 或你所在的公司可能会架设一个 web 代理来作为本地网络基础的一部分提供给用户。
这样热门的资源就会被重复使用,减少网络拥堵与延迟。
(三) 🦄浏览器的缓存策略
-
一个检索请求的成功响应: 对于 GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应;
-
不变的重定向: 响应状态码:301;
-
可用缓存响应:响应状态码:304,这个存在疑问,Chrome会缓存304中的缓存设置,Firefox;
-
错误响应: 响应状态码:404 的一个页面;
-
不完全的响应: 响应状态码 206,只返回局部的信息;
-
除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应;
以上,对于我们可以和应该缓存的目标有个了解。🤗
浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。
那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢❓响应头!响应头!响应头!重要的事情说三遍。✌️
我们看🌰:
Age:23146
Cache-Control:max-age=2592000
Date:Tue, 28 Nov 2017 12:26:41 GMT
ETag:W/"5a1cf09a-63c6"
Expires:Thu, 28 Dec 2017 05:27:45 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
1.强制缓存
以上请求头来自百度首页某个CSS文件的响应头。
- Expires
是HTTP/1.0中的定义缓存的字段,它规定了缓存过期的一个绝对时间
- Cache-Control:max-age=2592000
是HTTP/1.1定义的关于缓存的字段,它规定了缓存过期的一个相对时间。
优先级上当然是版本高的优先了 max-age > Expires
这就是强缓存阶段,当浏览器再次试图访问这个CSS文件,发现有这个文件的缓存,
那么就判断根据上一次的响应判断是否过期,如果没过期,使用缓存。
Firefox浏览器表现为一个灰色的200状态码。
Chrome浏览器状态码表现为:
200 (from disk cache)或是200 OK (from memory cache)
2.协商缓存阶段
那么当这个CSS文件过期了怎么办?ETag和Last-Modified就该闪亮登场了。
- Last-Modified
这个字段是文件最后一次修改的时间;
- ETag
ETag是对文件的一个标记,嗯,可以这么说,具体生成方式HTTP并没有给出一个明确的方式,
所以理论上只要不会重复生成方式无所谓,比如对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。
利用这两个字段浏览器可以进入协商缓存阶段,当浏览器再次试图访问这个CSS文件,发现缓存过期,
于是会在本次请求的请求头里携带If-Moified-Since和If-None-Match这两个字段,
服务器通过这两个字段来判断资源是否有修改,如果有修改则返回状态码200和新的内容,
如果没有修改返回状态码304,浏览器收到200状态码,该咋处理就咋处理(相当于首次访问这个文件了),
发现返回304,于是知道了本地缓存虽然过期但仍然可以用,于是加载本地缓存。然后根据新的返回的响应头来设置缓存。
(这一步有所差异,发现不同浏览器的处理是不同的,chrome会为304设置缓存,firefox则不会)😑
If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMT
If-None-Match: W/"5a1cf09a-63c6"
到这协商缓存结束
3. 启发式缓存阶段
我们把上面的响应头改下:
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
发现没?浏览器用来确定缓存过期时间的字段一个都没有!那该怎么办?
有人可能会说下次请求直接进入协商缓存阶段,携带If-Moified-Since呗,不是的,浏览器还有个启发式缓存阶段😎
根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。
看下面这张图,来解释浏览器整个缓存策略的过程:

👌对于缓存策略介绍到这,接下来再细细分析不同的HTTP首部字段的内容,以及它们之间的关系。
(四) 🦀HTTP中和缓存相关的首部字段
HTTP报文是什么呢?就是HTTP报文,这是一个概念,主要由以下两部分构成:
- 首部(header):包含了很多字段,比如:cookie、缓存、报文大小、报文格式等等);
- 主体(body):HTTP请求真正要传输的部分,比如:一个HTML文档,一个js文件;
以上我们知道浏览器对于缓存的处理过程,也简单的提到了几个相关的字段。🤧接下来我们具体看下这几个字段:
1. 通用首部字段
| 字段名称 | 说明 |
|---|---|
| Cache-Control | 控制缓存具体的行为 |
| Pragma | HTTP1.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-Modified-Since | 比较资源前后两次访问最后的修改时间是否一致 |
4. 实体首部字段
| 字段名称 | 说明 |
|---|---|
| Expires | 告知客户端资源缓存失效的绝对时间 |
| Last-Modified | 资源最后一次修改的时间 |
(五) 🦅浏览器缓存控制
HTTP/1.1一共规范了47种首部字段,而和缓存相关的就有以上12个之多。接下来的两个小节会一个一个介绍给大家。🤓
1. 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 | 无 | 不缓存请求或是响应的任何内容 |
| max-age=[秒] | 缓存时长,单位是秒 | 缓存的时长,也是响应的最大的Age值 |
| min-fresh=[秒] | 必需 | 期望在指定时间内响应仍然有效 |
| no-transform | 无 | 代理不可更改媒体类型 |
| must-revalidate | 无 | 可缓存但必须再向源服务器进确认 |
| proxy-revalidate | 无 | 要求中间缓存服务器对缓存的响应有效性再进行确认 |
| cache-extension | 无 | 新的指令标记(token) |
请注意no-cache指令很多人误以为是不缓存,这是不准确的,no-cache的意思是可以缓存,但每次用应该去想服务器验证缓存是否可用。
no-store才是不缓存内容。另外部分指令也可以组合使用,比如:
Cache-Control: max-age=100, must-revalidate, public
上面指令的意思是缓存的有效时间为100秒,之后访问需要向源服务器发送请求验证,此缓存可被代理服务器和客户端缓存
(六) 🐲 用户操作行为对缓存的影响
搜索了很久有没有关于这方面的权威总结,最后竟然在百度百科找到了也是很惊讶,
我自己加了一条用户强制刷新操作浏览器的反应。强制刷新,window下是Ctrl+F5,mac下就是command+shift+R操作了。:relieved:
| 用户对浏览器操作 | 说明 |
|---|---|
| 打开新窗口 | 如果指定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) |
:wink:如果想在浏览器点击“刷新”按钮的时候不让浏览器去发新的验证请求呢?办法找到一个,知乎上面一个回答,在页面加载完毕后通过脚本动态地添加资源:
$(window).load(function() {
var bg='http://img.infinitynewtab.com/wallpaper/100.jpg';
setTimeout(function() {
$('#bgOut').css('background-image', 'url('+bg+')');
},0);
});