IFE 2019年11月20日 分类: 前端知识体系 网络 标签: 浏览器 HTTP 浏览量: 4787
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
浏览器会根据 HTTP Header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦缓存下来,除非被清除或过期,不会再次去请求数据。
最常见的缓存位置:Memory Cache、Disk Cache。
Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200 OK (from cache),响应内容和之前的响应内容一模一样。
Expires 是 HTTP/1 的产物,表示资源会在一个具体时间后过期,Expires的值对应一个GMT(格林尼治时间),比如Mon, 19 Nov 2019 10:12:01 GMT来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。
需要注意的是,响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的,其定义的是资源“失效时刻”,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。
针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间。Cache-Control的优先级大于Expires,若报文中同时出现了 Expires 和 Cache-Control,则以 Cache-Control 为准。
Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。 Cache-Control 的格式为:
"Cache-Control" ":" cache-directive
作为请求首部时,cache-directive 的可选值有:
作为响应首部时,cache-directive 的可选值有:
如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。
服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。
客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码,内容为空;否则服务器会发回该资源并返回200状态码。
两种请求字段:
Last-Modified 存在一些弊端: 如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端 因为 Last-Modified 只能以秒计时,它无法处理文件一秒内多次修改的情况,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
了解决上述Last-Modified可能存在的不准确的问题,Http1.1还推出了 ETag 实体首部字段。优先级比 Last-Modified 高。
服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。其完全可以解决Last-Modified头部的问题,但是其计算过程需要耗费服务器资源。
客户端会保留该 ETag 字段,并在下一次请求时,将其带在请求头中发给服务器。 如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
两种请求字段:
注意:如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。
可以把刷新/访问界面的手段分成三类: 在URI输入栏中输入然后回车/通过书签访问 F5/点击工具栏中的刷新按钮/右键菜单重新加载 * Ctl+F5/强制刷新
当用户的浏览器第一次成功请求某份资源后,返回的http头会包含:Expires、Cache-Control、Last-Modified、Etag等信息,浏览器会对该文件进行缓存,直到该文件过期、用户清空cache或者用户强制刷新资源时间。
当用户再次请求该资源时,根据不用的访问行为会有不同的结果。
1、在URI输入栏中输入然后回车
如果浏览器发现该资源已经缓存了而且没有过期(通过Expires头部或者Cache-Control头部),就不会跟服务器确认,而是直接使用了浏览器缓存的内容。其中响应内容和之前的响应内容一模一样,比如Date时间是上一次响应的时间。Network中能看到该资源的Size为from cache
2、F5/点击工具栏中的刷新按钮/右键菜单重新加载
F5会让浏览器无论如何都发一个HTTP Request给Server,且自动加上Cache-Control: max-age=0
,即使先前的响应中有Expires头部。服务器会根据请求头中If-None-Match或If-Modified-Since信息以确认资源是否需要重新发送,如果资源没有修改,就返回了304(Not Modified),这样的响应信息很小,所消耗的route-trip不多,网页很快就刷新了。
3、Ctl+F5/强制刷新
Ctrl+F5要的是彻底的从服务器拿一份新的资源过来,请求头中不会带If-Modified-Since和If-None-Match,并且为了防止从Browser到Server之间的中间节点(比如Proxy)也可能扮演Cache的作用,还会包含如下两个头部信息,让中间的Cache对这个请求失效,这样返回的绝对是新鲜的资源。
Cache-Control: no-cache Pragma: no-cache
总结:
对于频繁变动的资源,首先需要使用Cache-Control: no-cache
使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
html文件一般不缓存或缓存时间很短,引用的css、js文件可以通过打包工具对文件名进行哈希处理,只有当代码修改后才生成新的文件名,这样可以设置长期的缓冲期,比如一年Cache-Control: max-age=31536000
,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。