对于浏览器缓存,相信很多开发者对它是又爱又恨,一方面,它可以极大地提升用户体验,另一方面,在开发中经常会由于浏览器缓存而展示了「错误」的东西。那么,浏览器缓存究竟是什么呢?它的内部机制又是怎样的?接下来就让我跟大家分享一下浏览器缓存机制。
简单来说,浏览器缓存就是把一个已经请求过的 web 资源拷贝一份存储在浏览器中,当下次请求相同的资源时,浏览器会根据缓存机制决定直接使用副本响应访问请求还是再次向服务器发送请求。
如下图所示是我在第二次打开某个网页时的资源请求图,可以看出里面大部分资源是从浏览器直接读取了缓存。
读取缓存资源
那么浏览器缓存究竟有什么作用呢?在这里我将浏览器缓存的作用简单地归结为以下几点。
加快页面打开速度
降低服务器压力
减少网络损耗
所以浏览器缓存虽然有时对我们开发者来说并不那么「友好」,但其对提升用户体验却有很大的帮助,接下来就让我们来学习浏览器缓存机制吧。
浏览器缓存有 HTML Meta 标签控制与 HTTP 头信息控制两种。
HTML Meta 标签控制
HTML Meta 标签控制是指在 html 页面中加入如下标签
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
上述代码的作用是告诉浏览器当前页面不被缓存,所以每当需要请求该页面时都需要去服务器获取。
但由于仅有部分浏览器支持该标签,并且所有的缓存代理服务器均不支持,所以并未被广泛使用。
HTTP 头信息控制
浏览器每次在向服务器发起 HTTP 请求获得请求结果(包含 HTTP 头信息各种字段)后,会根据响应报文中 HTTP 头的缓存标识字段(后面我们会知道这个字段就是 Expires 和 Cache-Control),来决定是否将请求结果存入浏览器缓存中。
浏览器每次在向服务器发起 HTTP 请求时,都会查找浏览器缓存中是否存在其相应的请求结果,然后根据缓存标识字段来决定其是直接使用之前缓存的副本还是再次向服务器发出 HTTP 请求。
对于每次浏览器第一次 HTTP 请求来说,浏览器缓存中并不存在其请求资源相应的副本,这时浏览器便会直接向服务器发出 HTTP 请求来获得相应的请求结果,并根据缓存标识字段,来决定是否将请求结果作为副本存入浏览器缓存中。
HTTP 保持已缓存数据与服务器数据之间充分一致的机制称为文档过期和服务器再验证。而从浏览器缓存分类来看,也有将其分为强制缓存和协商缓存。
下面我就文档过期和服务器再验证的机制做详细的介绍,下面表述的过程都是指浏览器缓存中已经存在其相应资源副本的情况。
文档过期
当浏览器发起 HTTP 请求时,会根据浏览器缓存中的缓存标识字段来验证文档(资源副本)是否过期。
上述说的缓存标识字段便是 Expires 和 Cache-Control。
Expires 是服务器端在响应请求时用来规定资源的失效时间。
Cache-Control 是服务器端在响应请求时用来规定资源是否需要被浏览器缓存以及缓存的有效时间等。
「Response Headers」Expires 与 Cache-Control
Cache-Control 主要取值如下:
public:所有内容都将被缓存(客户端和代理服务器都可缓存)
private:内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
no-cache:必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌(ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载
no-store:所有内容都不会被缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation:如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx:缓存的内容将在 xxx 秒后失效
这里需要注意的是,no-cache 的作用是指跳过文档过期的验证而直接进行服务器再验证,而 no-store 是指资源禁止被缓存。
Expires 是 HTTP 1.0 的字段,而 Cache-Control 是 HTTP 1.1 的字段,当 Expires 与 Cache-Control 同时存在时,Cache-Control 的优先级要高于 Expires。
在浏览器缓存中根据 Expires 和 Cache-Control 的值来验证文档(资源副本)是否过期的过程,称为 HTTP 的文档过期验证机制。若是文档没有过期,则浏览器会直接使用缓存中的文档作为返回结果,若是文档已经过期了,则需要进行服务器再验证。
服务器再验证
在浏览器缓存中,还保存了其它关于资源副本的描述字段,这些字段都是服务器返回信息头带过来的,如 Last-Modified 和 Etag。
「Response Headers」Etag
Last-Modified 是服务器端在响应请求时用来说明资源的最后修改时间。与之对应的是 If-Modified-Since 字段,在服务器再验证过程中,浏览器发送的 HTTP 请求的请求头中会带上 If-Modified-Since 字段,值为该资源 Last-Modified 属性的值。
当服务器端接收到带有 If-Modified-Since 属性的请求时,则会将 If-Modified-Since 属性的值与被请求资源的最后修改时间做对比。如果相同,说明资源没有新的修改,则响应 HTTP 304,浏览器会继续使用原先保存的该资源的副本;如果最后修改时间比较新,则说明资源被修改过,则响应 HTTP 200,并且返回最新的资源。
Etag 是服务器端在响应请求时用来说明资源在服务器端的唯一标识。与之对应的是 If-None-Match 字段,在服务器再验证过程中,浏览器发送的 HTTP 请求的请求头中会带上 If-Modified-Since 字段,值为该资源 Etag 属性的值。
当服务器端接收到带有 If-None-Match 属性的请求时,则会将 If-None-Match 属性的值与被请求资源的唯一标识做对比。如果相同,说明资源没有新的修改,则响应 HTTP 304,浏览器会继续使用原先保存的该资源的副本;如果不同,则说明资源被修改过,则响应 HTTP 200,并且返回最新的资源。
「Request Headers」If-None-Match
那么当 Last-Modified / If-Modified-Since 和 Etag / If-None-Match 同时存在是什么情况呢?
事实上,当两者同时存在时,Etag / If-None-Match 的优先级要高于 Last-Modified / If-Modified-Since,HTTP 1.1 中 Etag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:
Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内被修改多次的话,它将不能准确标注文件的修改时间;
如果某些文件会被定期生成,但有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存;
有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形;
下面用一张流程图来完整说明当浏览器发起 HTTP 请求时缓存机制的过程:
浏览器缓存机制
最后,虽然说浏览器缓存对用户体验有极大的好处,但是作为开发者,我们在开发的时候则需要禁止这「讨厌」浏览器缓存,我的方法是打开浏览器的开发者工具,在 Network 中有个 Disable cache ,钩上就可以了,钩上后浏览器会忽略掉文档过期验证和服务器再验证的过程,直接向服务器请求最新的资源。
谷歌开发者工具禁用浏览器缓存
作者:淘淘笙悦
链接:https://www.jianshu.com/p/8b4f3f7bf823