当前位置: 首页 > Web前端 > JavaScript

本文彻底理解前端缓存机制_1

时间:2023-03-27 16:12:03 JavaScript

浏览器缓存步骤1)浏览器加载资源时,首先根据资源的一些http头判断是否命中强缓存。如果强缓存命中,浏览器直接从自己的缓存中加载资源读取资源,不会向服务器发送请求。比如一个css文件,如果css文件的缓存配置在浏览器加载所在网页时命中了强缓存,那么浏览器会直接从缓存中加载css,甚至不会发送请求到网页所在的服务器;2)当强缓存没有命中时,浏览器肯定会向服务器发送请求,通过服务器根据资源的其他一些http头来验证资源是否命中了协商缓存。如果协商缓存命中,服务器将返回请求。但是它不会返回这个资源的数据,而是告诉客户端直接从缓存中加载这个资源,这样浏览器就会从自己的缓存中加载这个资源;3)强缓存和协商缓存的共同点是:如果命中,资源从客户端缓存加载,而不是从服务端加载资源数据;不同的是:强缓存不向服务器发送请求,协商缓存向服务器发送请求。4)协商缓存未命中时,浏览器直接从服务器加载资源数据。示例:以一个常见的CSS样式请求为例。对于第一个请求,服务器通常会发送这4个字段,可能是全部4个,也可能一个都不发送。这里主要说明四个字段都存在的情况。对前端的第二次请求:首先,浏览器会检查Cache-Control和Expires。如果有Cache-Control,就以它为标准。如果超时,就会向后端发送请求,请求中会带上If-Modified-Since,If-None-Match。背景:后台服务器收到请求后,会比较这两个字段,同样以If-None-Match为准。如果没有If-None-Match,则比较If-Modified-Since。如果对比发现文件没有过期,即Etag没有变化,或者Last-Modified与If-Modified-Since一致(仅当If-Modified-Since存在时)。如果改变,则发送一个新文件,否则,直接返回304。浏览器缓存分类强缓存当客户端第一次向服务器请求某个资源时,服务器将客户端请求的资源抛回,同时告诉客户端将资源保存在本地,在某个时刻在future之前如果还需要这个资源,可以直接从本地获取,不需要向服务端请求。以这种方式缓存的资源称为强缓存。强缓存由HTTP返回头中的Expires(实体头字段)或Cache-Control(通用头字段)两个字段控制,用于指示资源的缓存时间。服务器通过这两个头域通知客户端资源缓存过期时间和缓存最大生存期。客户端知道资源的缓存过期时间和最大生命周期后,就可以判断是否可以直接从浏览器缓存中获取资源,而无需与服务器建立连接。当命中强缓存时,浏览器也会收到status=200的响应。在Chrome中,可以通过大小来区分是服务器返回的资源还是强缓存获取的资源。Expires该字段是http1.0的标准,取值为GMT格式的绝对时间字符串,代表缓存资源的过期时间。在这个时间点之前,缓存被命中。流程如下:当浏览器第一次向服务器请求资源时,当服务器返回资源时,会在相应的header中添加Expires,如图:clipboard.png浏览器收到后资源,它会把资源和响应头一起缓存起来;当浏览器再次请求该资源时,会先从缓存中查找该资源,然后将Expires时间与当前请求时间进行比较。如果Expires时间晚于当前浏览器请求时间,说明缓存还没有Expires,即命中缓存;如果当前请求时间晚于Expires,则说明缓存已过期,即未命中缓存,浏览器会向服务器发送请求申请资源。缺点:服务器返回的Expires时间点是服务器上的时间,可能和客户端有时间差。如果时间差太大,可能会造成缓存混乱。Cache-Control:max-ageCache-Control有很多属性,不同的属性代表不同的含义。private:客户端可以缓存public:客户端和代理服务器都可以缓存都会缓存。该字段是http1.1的规范。强缓存使用其max-age值来确定缓存资源的最大生命周期。它的取值单位是秒。Cache-Control:max-age=3600表示缓存资源的有效时间为1小时,即从第一次获取资源起1小时以内的请求才算命中强缓存。流程如下:浏览器第一次向服务器请求资源,当服务器返回资源时,会在相应的header中添加Cache-Control:max-age=XXXXXXXX,如图:clipboard.png浏览器收到资源后,连同响应头一起缓存;当浏览器再次向服务器请求同一个资源时,会先从缓存中查找资源,获取date(第一次资源返回的响应时间)和max-age,计算出一个有效期(date+max-age),然后与浏览器请求时间进行比较,最终判断是否命中缓存(逻辑同Expires);如果没有命中缓存,浏览器直接向服务器请求资源,当资源返回时Cache-Control会重新获取服务器的Updates。Cache-Control描述的是相对时间,使用本地时间来计算资源的有效期,因此比Expires更可靠。这两个Header可以只使用其中一个,也可以一起使用。一起使用时以Cache-Control为准。协商缓存当客户端第一次向服务器请求某个资源时,服务器将请求的资源返回给客户端,同时返回该资源的一些信息(文件摘要,或者最后修改时间)给客户端,告诉客户端在本地缓存这个资源。当客户端下次需要这个资源时,会把请求和相关信息(文件摘要,或者最后修改时间)一起发送给服务器,服务器判断是否需要更新客户端缓存的资源:如果不需要需要更新,直接告诉客户端获取本地缓存资源即可;如果需要更新,将最新的资源连同相应的信息返回给客户端。当强缓存未命中时,浏览器会向服务器发送请求,服务器会验证是否命中了协商缓存。如果命中协商缓存,则请求返回的HTTP状态为304,并会显示NotModified的描述,浏览器收到返回后,会从缓存中加载。协商缓存由两对标头[Last-Modified,If-Modified-Since]和[ETag,If-None-Match]管理。Last-Modified&If-Modified-SinceLast-Modified为实体头域,值为资源最后更新时间,随服务器响应返回。If-Modified-Since是请求的头域。通过比较两次,判断资源在两次请求过程中是否被修改过。如果没有,就会命中协商缓存,浏览器会从缓存中获取资源;如果已经被修改,则服务器返回资源,同时返回新的Last-Modified时间。流程如下:当浏览器第一次请求服务器资源,服务器返回资源时,会在响应头中添加Last-Modified,表示该资源在服务器上的最后修改时间;当请求资源时,If-Modified-Since将被添加到请求标头中。该值是服务器上次返回的Last-Modified时间;与服务器上最后修改时间比较,如果If-Modifid-Since与最后修改时间一致,则命中缓存,服务器返回304,浏览器从缓存中获取资源;如果没有命中缓存,服务器返回资源,浏览器缓存资源的Last-Modified将被更新。ETag&If-None-Match在某些情况下,仅判断最后修改日期不足以验证资源是否发生变化:有些资源会定期重写,但资源的实际内容没有变化;修改的信息不重要,如评论等;Last-Modified不能精确到毫秒,但是有些资源的更新频率有时不到一秒。为了解决这些问题,http允许用户给资源打上标签(ETag)来区分两条相同路径获取的资源内容是否一致。通常,使用MD5等加密哈希函数对资源进行编码以获得标签(强验证器);或通过版本号,例如W/“v1.0”(W/表示弱验证器)。ETag为对应的头域,表示资源内容的唯一标识,随服务器响应返回;If-None-Match为请求头字段,服务器比较请求头的If-None-Match是否与当前资源的ETag一致。确定资源是否在两次请求之间被修改。如果没有修改,就会命中协商缓存,浏览器会从缓存中获取资源。如果有修改,服务器会同时返回资源和新的ETag。流程如下:当服务器第一次收到浏览器的资源请求时,会在响应头中添加ETag。这个ETag是基于资源生成的唯一标识。这个唯一标识符是一个字符串。应提供更改和新资源,然后ETag必须更改。浏览器将资源与ETag一起缓存。浏览器再次向服务器发送资源请求时,会在请求头中添加If-None-Match,即服务器第一次返回的ETag值;服务器收到资源请求后,会对请求的资源重新计算生成对应的ETag,然后与If-None-Match进行比较。比较结果一致则命中缓存,不一致则不命中缓存。资源将被返回,同时新的ETag将被发送到浏览器。协商缓存管理[Last-Modified,If-Modified-Since]和[ETag,If-None-Match]一般是同时开启的,这是为了处理不可靠的Last-Modified。参考视频讲解:在学习前端部署方案之前,大家可能都知道一般公司处理静态资源和缓存的方式只有那么几种。1在静态资源后面加一个版本号v=1.111和上面的方法类似。2为了准确判断文件是否被修改,将后面的版本号改为文件摘要(主要根据文件内容生成的值)。和上面类似,后面红框标示的部分就是根据文件的summary生成的key.3。直接将资源文件名拼接成文件名使用文件摘要或者固定字符串加文件摘要。和上面的方法类似,最后红圈标示的代码是根据文件摘要生成的。这里需要和第二种方法区分开来。第二种方法是把它放在url后面作为参数,但是文件名不变。而这里直接选择修改文件名。(彩蛋:有意思,找了几个TX网站,发现不是所有的网站都用最后一种方式,我觉得应该用技术来追求完美,但是实现还是靠人来实现的,毕竟人是我的天生懒惰。)那么问题来了?以上三种方法有什么区别呢?为什么最后会变成第三条路呢?1第一种方式需要维护版本号。如果一个文件中有多个资源,未修改的资源文件的版本号也会被修改,造成不必要的资源加载。(当然,如果需要加时间戳之类的,不属于第一种的范围。)2第二种方法可以准确的找出哪个文件被修改了。从而要求客户端重新加载。但是也会出现一些问题。一般能做到第二种方法的公司,网页流量可想而知(小公司请自动忽略)。那么在发布版本的时候,就会有两类文件需要发布:1)引用了资源文件的html文件2)资源文件,那么以上两种文件的发布顺序就成了问题。如果先发送html文件:会导致重新加载资源,但仍然无法访问最新功能。(毕竟资源文件还没有更新。)如果更新了Html页面的结构,却加载了旧的资源,极有可能造成页面结构混乱。并且资源会一直缓存到资源过期为止,否则一直是错误页面,除非强制刷新。(这里要注意,由于第一次加载旧资源,版本号是新版本号,所以即使之后再上传资源,这里还是会读取旧资源)如果资源filefirstsent:如果之前访问过该页面,本地会有缓存,所以不会有问题,因为访问的还是缓存文件。但是如果是新用户,那么会访问一个新的资源文件,很可能会造成页面混乱。并且页面html释放后,页面恢复正常。PS:当然,可能有人会说,放出来只是一会而已,有必要在意这些小瞬间吗?如果你这么认为,那我只能说,我无话可说。两个贴子都是覆盖资源发布,无论怎么处理都会出现这样的问题。那么解决方案就是第三种。发布无覆盖。3第三种方法应该是最完美的解决方法:1.先发送资源文件。由于文件名不同,已经存在的资源文件不会被覆盖,客户端仍然可以安全访问。2再次发送客户端文件。一旦客户端文件发布成功,马上就会切入新的特征,中间可以无缝对接。这就是所谓的非覆盖发布方案。