前言
浏览器缓存对开发者来说一直都是一个有爱又恨的存在,一方面帮助开发者提升用户体验,另一方面有时会抽风,读取缓存展示错误的内容,因此,希望对浏览器缓存做一个总结,避免开发时因为缓存机制而过多耗费时间。接下来,就进入浏览器缓存的世界
什么是缓存
缓存是指一个资源存在于服务器和客户端之间的副本,缓存会根据请求保存输出内容的副本,当下一个请求进来的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应还是向源服务器重新请求,当你访问一个网站时,打开调试面板,你会发现几乎静态资源请求都是从缓存中读取出来的,如图:
为什么要缓存
不用缓存可以吗?当然可以,至于后果是什么?试了就知道。用了缓存之后,会有什么好处:
- 减少网络带宽消耗。无论对于网站运营方还是用户,带宽都是和钱挂钩的,当使用缓存时,产生的网络流量是极小的,对于两边都可以降低开销
- 降低服务器压力。当使用缓存时,可以有效减少用户对源服务器的请求,从而降低服务器压力
- 加快网页打开速度,提升用户体验。请求缓存比请求源服务器所话费的时间要短的多,因此内容可以更快的触达用户,以提升体验
浏览器缓存
前菜结束上硬菜,本篇主角浏览器缓存,浏览器缓存是众多web缓存分类中的一种,主要分为:
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
Memory Cache
Memory Cache指的是内存中的缓存,它有如下几个特点:
- 响应速度最快,是浏览器请求时最先去尝试命中的缓存
- 生命周期短,一旦进程被关闭就会被清空
- 内存有限,资源存放位置随机
- 不关心资源的HTTP缓存头的Cache-Control值,在同一个进程会被重用
来看一个例子:
第一张图是第一次打开网页时截取的,圈圈标记的图片时存放在Disk Cache里面的,第二张图时网页刷新时截取的,圈圈标记的图片是存放在Memory Cache中的,同样的图片两次为什么是从不同的缓存中读取的呢?因为Memory Cache进程关闭时就会清空,但是第二次刷新的时候,第一次浏览器解析图片文件进入Memory Cache,第二次刷新时由于Memory Cache是浏览器请求时最先去尝试命中的缓存,因此会直接去从Memory Cache中取
Service Worker Cache
Service Worker是一种独立于主线程之外的JavaScript线程,不会对当前程序的执行线程造成堵塞,通过Service Worker我们可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,通过Service Worker实现的缓存称为Service Worker Cache,如果你对Service Worker有更深入了解的兴趣,可以去看看我之前的一篇博客:传送门
HTTP Cache
HTTP Cache是我们日常开发中接触最多也是最为熟悉的缓存,HTTP Cache通常可以分为强缓存和协商缓存,缓存策略都是通过HTTP Header来实现的,强缓存的优先级高于协商缓存,在命中强缓存失败的情况下才会去命中协商缓存
强缓存
强缓存主要通过设置HTTP Header Expires和Cache-Control两个字段控制,当请求发出时。浏览器会根据上一次请求时记录的Expires和Cache-Control来判断是否命中强缓存,若命中则直接从强缓存中取资源,不会再向服务发送请求,当命中强缓存时,HTTP状态码返回200,且Size显示from disk cache,如图所示:
Expires
Expires缓存过期时间,值为一个时间戳。如图:
当我们两次向同一个服务器请求资源时,第二次请求时,浏览器会先对比Expires时间戳和本地时间,如果本地时间小于Expires时间戳就会去缓存中取资源,但是这样存在一个问题,Expires依赖客户端时间,如果客户端时间和服务端时间不一致时,就会产生问题,Expires是HTTP1.0标准下的字段,考虑到这个问题,因此在HTTP1.1标准下新增了Cache-Control字段
Cache-Control
Cache-Control字段是Expires字段的完全替代方案,它做Expires字段能做的所有事,还有Expires字段不能做的事情,当前还在用Expires字段的目的只是向下兼容,Cache-Control字段包含多个指令,这里介绍几个最常用的:
- max-age:max-age指令控制资源的有效期,值为时间长度,如图:
当客户端发送的请求中包含max-age指令时,浏览器会向服务器确认缓存的有效性,如果判定缓存资源的缓存时间数值比指定的时间数值小,那么客户端就接收缓存资源,当指定max-age值为0时,通常会向服务器发送请求
当服务器返回的响应中包含max-age指令时,表示这段时间内,响应由缓存控制,浏览器不会再向服务器确认资源的有效性,而是直接返回缓存
- s-maxage:s-maxage指令的功能和max-age指令的相同,不同点是s-maxage指令只适用于供多位用户使用的公共缓存服务器(代理服务器),s-maxage的优先级高于maxage,当s-maxage未过期时,会向公共缓存服务器请求缓存
- public指令与private指令:当使用public指令时,则表示此缓存是公有缓存,可以被其他用户使用,当使用private指令时,表示该缓存时私有缓存,只有在特定用户请求时才会返回缓存
- no-cache:防止从缓存中返回过期资源,当客户端请求中包含no-cache指令,表示客户端将不会接收缓存过的响应,缓存服务器必须把客户端请求转发给源服务器,当服务器返回的响应中包含no-cache指令,缓存服务器不能对资源进行缓存,存储在本地缓存区中缓存在与源服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用
- no-store:不使用任何缓存,直接向源服务器请求下载内容
协商缓存
协商缓存就是强制缓存失效后,浏览器浏览器携带缓存标识向服务发起请求,由服务器更具缓存标识决定是否返回缓存,主要有两种情况:
- 如果服务端提示资源未改动,资源会被重定向到浏览器缓存,这种情况下对应的网络状态码为304,如图:
- 协商缓存失败,资源更新了,重新返回请求结果,这种情况下对应的网络状态码为200
接下来介绍和协商缓存相关的头部字段:
Last-Modified/If-Modified-since
Last-Modified指明资源最终修改的时间,值为一个时间戳,如图:
当缓存要对已缓存的文档进行再验证时,请求头中就会包含一个If-Modified-since首部,其携带有此资源最后修改的时间戳,如图:
如果在此期间内容被修改,最后的修改日期就会有所不同,源服务器就会返回新的内容重新响应,否则就会返回304,重定向到浏览器缓存。
使用Last-Modified存在两个弊端:
- 我们打开了文件,但是并没有修改文件内容,服务器还是会认为我们修改了这个文件,Last-Modified会被更新,下次请求时会重新响应
- If-Modified-since只能感应以秒为最小单位的时间差,当改动文件速度过快,小于1s时,无法感知文件变化,导致应该重新请求时,拉到缓存资源
由于这些缺陷HTTP1.1出现了Etag/If-None-Match
Etag/If-None-Match
ETag能告知客户端实体标识,它是一种可将资源以字符串形式做唯一标识的方式。服务器会为每份资源分配对应的ETag值,当资源更新ETag值也会更新,如图:
当我们下次请求时,请求头里会带上一个If-None-Match字符串提供服务端对比,如图:
如果服务器上的标签已经发生了变化,服务器会在一个200响应中返回新的内容以及新的ETag,否则返回304重定向到缓存
HTTP缓存决策
此图源自google,清楚展示了HTTP Cache决策的过程,对上面介绍的缓存过程做了一个完美的总结
Push Cache
Push Cache指HTTP2在server push阶段存在的缓存,是HTTP2 session的一部分,不是一个持久化的缓存,当session结束时,缓存也会随之结束,不同的页面只要共享了同一个HTTP2连接,那么它们就可以共享同一个Push Cache,如果你对Push Cache还有更多的兴趣,这里提供三篇文章供你阅读:
总结
此篇文章记录总结了浏览器缓存相关的一些知识点,是个人最近对缓存知识的一个总结,希望对大家也能有所帮助。
如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。