"我这边把代码更新上服务器了,你那边看一下呢?"
"我这边还有这个问题,你改没改哦?"
"我改了啊,不信你看我代码......噢,可能是浏览器缓存问题, 你F5刷新一下试试,如果不行 就Ctrl + F5强刷一次试下"
"我强刷了一次,这下可以了"
以上对话大家可能会比较熟悉,可能被浏览器的缓存问题困扰了不久,让我们今天就来揭开浏览器缓存的神秘面纱。
浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。
以上是百度百科对于浏览器缓存的定义
可见浏览器缓存目的:为了节约网络的资源加速浏览
而方式是:对请求的文档进行存储
而文档具体就是指:html , js , css , 图片,其他媒体文件等
如果对所有请求的资源都进行缓存,那肯定是不行的。所以肯定是需要一套规则来指定什么资源应该存在浏览器中,存多久。而这套规则就是http协议。
浏览器根据该请求返回的响应头(respone header)的内容来决定该资源是否会被缓存,会存多久。
浏览器缓存可以分为两类:
我们分别来看一下这两个http header
Cache-Control
浏览器缓存里, Cache-Control是金字塔顶尖的规则, 它藐视一切其他设置, 只要其他设置与其抵触, 一律覆盖之.不仅如此, 它还是一个复合规则, 包含多种值, 横跨 存储策略, 过期策略 两种, 同时在请求头和响应头都可设置.
语法为: “Cache-Control : cache-directive”.
上文的max-age=3600即代表该资源会在浏览器缓存3600秒,即1个小时。在获取到该资源后的1小时内,若浏览器再一次请求该资源,将不会发出网络请求,直接读浏览器的缓存中读取。
假设所请求资源于4月5日缓存, 且在4月12日过期.
当max-age 与 max-stale 和 min-fresh 同时使用时, 它们的设置相互之间独立生效, 最为保守的缓存策略总是有效. 这意味着, 如果max-age=10 days, max-stale=2 days, min-fresh=3 days, 那么:
由于客户端总是采用最保守的缓存策略, 因此, 4月9日后, 对于该资源的请求将重新向服务器发起验证.
Expires
这是 Http/1.0 规定的响应头,它的含义就是代表该资源在未来某个时间点失效,比如:
Expires: Thu, 25 May 2020 12:30:00 GMT
代表该资源在2020年5月25日12点30分(格林威治时间)失效。
Expires有一个很大的弊端,就是它返回的是服务器的时间,但判断的时候用的却是客户端的时间,这就导致Expires很被动,因为用户有可能改变客户端的时间,导致缓存时间判断出错,缓存可能失效。
上面的例子中,我将客户端的时间改成Thu, 25 May 2020 12:31:00 GMT,浏览器 这也是引入Cache-Control:max-age指令的原因之一。
因为Cache-Control:max-age没有依靠客户端的时间,就算客户端时间改变到了本应过期的时间,而实际时间并没有达到过期时间,缓存不会失效。
协商缓存字段之ETag
服务器可以通过某种自定的算法对资源生成一个唯一的标识(比如md5标识),然后在浏览器第一次请求某一个URL时把这个标识放到响应头传到浏览器,浏览器会把这个ETag的值存起来,服务器端的返回状态会是200。
就像下面这张图一样。
以后如果浏览器要再发送该请求,会在request header 中加上If-None-Match(具体格式看下图), 而该请求头的值就是上一次存的ETag的值,用以发送给服务端来验证资源有没有修改。
Get请求中,当且仅当服务器上没有任何资源的ETag属性值与这个首部中列出的相匹配的时候,服务器端会才返回所请求的资源,响应码为200。
如果有资源的ETag值相匹配,那么返回304状态码。浏览器就会从缓存中获取该请求资源,从而达到节省开销加快用户访问速度的目的
协商缓存字段之Last-Modified
当Response Header中没有ETag,Cache-Control,Expires,Pragma这类缓存相关字段,只有Last-Modified,浏览器也会缓存,理论上,应该会在下一次请求中带上If-Modified-Since的请求头,去服务端验证资源是否过期,过期就响应码就为200并返回相应的资源,没过期响应码就是304,浏览器会从缓存中获取资源。
但实际上,各个浏览器对这部分的实现不太相同。
Chrome浏览器对此的缓存机制是,浏览器会有一套算法去判断当前资源是否过期。
如果没有过期的话,直接从缓存中取资源,不会发送请求。
用fiddler可以看到第一次请求了很多JS和css文件,但第二次请求 却没有再请求相关资源了,说明直接从缓存读取。
所以chrome浏览器有自己一套算法(下文会提到该算法)来判断该资源是否过期,如果没过期就直接读取缓存,而过期了才会去服务端验证该资源是否过期。
而FireFox不一样,如果是在上面的情况下缓存了该资源,会直接请求服务器来验证资源是否过期,如下图。
在测试中,我发现在状态码304的情况下,该测试服务器返回了 一个Etag字段,但下次请求,浏览器并未在请求头中加上 If-None-Match字段, 我猜测304状态码下的Etag, 不会触发该字段的缓存机制。
大多数浏览器都是像Chrome一样的做法。
Chrome浏览器自己对于Last-Modified的缓存计算如下:
如下资源便采取了启发式缓存算法.
根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。计算如下:
const Date_value = new Date('Thu, 06 Apr 2017 01:30:56 GMT').getTime(); const LastModified_value = new Date('Thu, 01 Dec 2016 06:23:23 GMT').getTime(); const cacheTime = (Date_value - LastModified_value) / 10; const Expires_timestamp = Date_value + cacheTime; const Expires_value = new Date(Expires_timestamp); console.log('Expires:', Expires_value); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (CST)
可见该资源将于2017年4月18日23点25分41秒过期, 尝试以下两步进行验证:
2) 然后又修改本地时间为2017年4月18日23点26分40秒(即往后拨1分钟), 刷新页面, 发现缓存已过期, 此时浏览器重新向服务器发起了验证, 且命中了304协商缓存, 如下所示.
可见, 启发式缓存算法采用的缓存时间可长可短, 因此对于常规资源, 建议明确设置缓存时间(如指定max-age 或 expires)
缓存字段之Pragma
http1.0字段, 通常设置为Pragma:no-cache, 作用同Cache-Control:no-cache.该字段通常作为兼容字段出现, 当一个no-cache请求发送给一个不遵循HTTP/1.1的服务器时, 客户端应该包含pragma指令. 为此, 勾选☑️ 上disable cache时或者Ctrl+F5强制刷新时, 浏览器自动带上了pragma字段和Cache-control:no-cache. 如下:
不同的网站应该有不同的缓存策略,应该结合网站的业务来制定适合的缓存策略。
下面简单聊几种情况
对于基于webpack打包构建生成的spa项目
很久都不会发生变化的静态文件,可以设置长时间的强缓存
比如说jquery的cdn就可以看到设置的强缓存100年- -`
cache-control: max-age=315360000
对于经常发生变化的静态资源,可以设置etag来使用协商缓存
参考:
浏览器缓存机制剖析
Caching best practices & max-age gotchas
best-practices-for-cache-control-settings-for-your-website