前端项目中 浏览器缓存的更新不及时问题及解决方法
web网站css,js更新后客户浏览器缓存问题,需要刷新才能正常展示的解决办法
前端开发中会遇到的问题,我们更新已上线的项目,用户的浏览器显示的却是旧版的页面,没有及时获取到我们更新的资源,这是什么原因造成的?此时,如果用户刷新一下页面,就得到更新后的资源,又是为什么?
答案是浏览器缓存。
浏览器缓存是前端优化的一个重要问题,缓存可以带来很多好处:
(1)减少冗余的数据传输,节省带宽;
(2)减轻服务器的请求负担,有缓存就可以少向服务器发送请求,尤其是对于一些访问量大的网站这点还是很重要的;
(3)资源从缓存中读取,无需向服务器发送请求再等待返回,加快了客户端的访问速度。
但是缓存同样给前端带来了一个很严重的问题,就是上面所说的项目更新的问题。如果项目更新了,但是用户访问时浏览器读取的是缓存资源,那么用户就获取不到最新的页面,影响用户使用。
接下来就从浏览器缓存开始分析出现项目更新问题的原因,并给出相应的解决方法。
浏览器缓存主要指http缓存,其机制是根据http报文的缓存标识进行相应操作。
一、http状态码
在讨论浏览器缓存之前,我们先看看网页相关的http状态码,打开控制台,在Network下捕捉请求,注意Status和Size栏,会看到200状态 from disk cache。
刷新页面,会看到304、200 from memory cache。
这几个有什么不同呢?200和304是常见的两个http状态码,200表示文件发生改动,304表示文件未改动,都是服务器返回告知的。在上面两张图中,我们注意到,有些200是灰色的,灰色的200表示没有向服务器发送请求,而是直接从缓存中读取。从缓存中读取又分为从内存(from memory cache)中读取还是从磁盘中读取(from disk cache)。
总结一张表。
200 from memory cache |
状态码是灰色的,从内存中读取之前已经加载过的资源,不请求服务器,页面关闭时,资源就会被内存释放,再次打开相同页面不会出现此类情况,在同一页面刷新才会出现。一般脚本、字体、图片会存在内存当中 |
200 from disk cache |
状态码是灰色的,从磁盘中读取之前已经加载过的资源,不请求服务器,页面关闭不会被释放,这部分资源存在电脑磁盘里,只有用户手动清除浏览器缓存的时候才会释放。一般非脚本会存在内存当中,如css等 |
200 数值大小 |
从服务器下载最新资源,数值是从服务器获取的全部资源大小 |
304 数值大小 |
访问服务器,发现资源没有更新,使用本地资源。数值是与服务器通信报文的大小,并不是资源本身的大小。 |
浏览器有三级缓存原理
1、先查找内存,如果内存中存在,从内存中加载;
2、如果内存中未查找到,选择硬盘获取,如果硬盘中有,从硬盘中加载;
3、如果硬盘中未查找到,那就进行网络请求,加载到的资源缓存到硬盘和内存;
结合我们的问题,假设某个用户打开过我们的页面,然后我们更新了文件,用户再去访问,资源加载情况是200 from disk cache,浏览器根本没有请求服务器,所以拿不到新的资源文件。那么,有没有办法解决这种情况?浏览器缓存机制是什么样的?
二、浏览器缓存
浏览器缓存分两种:强制缓存和协商缓存(对比缓存)
1、强制缓存
强制缓存就是,用户第一次访问页面之后,浏览器将数据存在缓存中,在过期时间之内,都不会再请求服务器。是否使用强制缓存在于资源是否过期,该过期时间从第一次请求的服务器响应头中获取。如果在过期时间内,从缓存中读取,如果超出过期时间,则使用协商缓存(下面会讲)。
控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。
上面的200 from memory cache和200 from disk cache属于强制缓存。
2、协商缓存
协商缓存,从字面意思,就是要协商,是浏览器和服务器协商,那么浏览器每次都要和服务器通信。在第一次请求服务器时,服务器会返回资源,并且返回一个资源的缓存标识,一起存到浏览器的缓存数据库。当第二次请求资源时,浏览器会首先将缓存标识发送给服务器,服务器拿到标识后判断标识是否匹配,如果不匹配,表示资源有更新,服务器会将新数据和新的缓存标识一起返回到浏览器;如果缓存标识匹配,表示资源没有更新,并且返回 304 状态码,浏览器就读取本地缓存服务器中的数据。
与协商缓存有关的字段是Last-Modified/IF-Modified-Since、Etag/IF-None-Match。
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
强制缓存和协商缓存的具体参数及分析比较可以参考以下文章
http://caibaojian.com/browser-cache.html
https://segmentfault.com/a/1190000016199807
再回到我们的问题,用户打开浏览器发现是旧的资源,于是手动刷新了一下页面,得到了新的资源,这是为什么呢?
3、用户行为会对缓存产生影响
结合上面的内容,我们可以分析情况了
1、用户第一次访问页面——200 数值大小,与服务器通信,服务器返回全部资源大小,浏览器把获取到的数据根据缓存规则进行缓存。
2、更新项目,用户打开页面是旧的资源——200 from disk cache,命中强缓存,使用了本地缓存,没有请求服务器
3、用户手动刷新页面,得到新资源——200 数值大小,用户刷新,强缓存失效,使用协商缓存,根据缓存标识发现资源修改了,服务器返回全部新资源和缓存标识
4、用户再刷新页面——304 数值大小,协商缓存,资源没有修改,数值不是全部资源大小,是报文信息大小。同时,这里会出现一些资源200 from memory的情况,它们存在内存中。
我们再看几张图应该就很好理解了
三、解决方法
明白了问题所在,我们接下来就可以提出对应的解决方法了。
以vue为例,vue在打包的时候,css和js名字都加了哈希值,所以改动后打包生成的js和css是唯一的,页面请求的是新资源,不会有缓存问题。但是入口文件index.html会因为缓存造成更新问题,如果我们更新了,但是浏览器使用的是缓存,就会出现问题。所以需要对入口文件设置不使用强制缓存,需要每次去服务器验证文件是否修改,即使用协商缓存。
使用nginx反向代理,在nginx.conf文件的对应server中设置,目前我自己实践出的可行的一种写法是:
server { listen 80; server_name 域名; root 文件目录; index index.html; location / { // 不加这一句,会出现nginx欢迎页面,无法正确加载资源 try_files $uri /index.html; } location ~ .*\.(html)$ { // 对html文件限制缓存 add_header Cache-Control no-store; // 不缓存 // 或者用add_header Cache-Control no-cache;替代上面那一句,协商缓存 add_header Pragma no-cache; } }
(1)Cache-Control: no-cache和Cache-Control: no-store区别
看字面意思容易误解,no-cache就是不缓存,但是no-cache并不是不缓存,而是使用协商缓存,所以并不能禁止缓存,no-store才是真正的禁止缓存。从节省带宽角度讲,使用no-cache更优一点,文件未发生改变时只传输很小的报文大小,只有在文件改变时才会传输整个文件大小。而不是no-store不管什么情况都传输整个文件大小。
(2)Pragma: no-cache:和Cache-Control: no-cache区别
Pragma: no-cache跟Cache-Control: no-cache相同,Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。因此,Pragma: no-cache可以应用到http 1.0 和http 1.1,而Cache-Control: no-cache只能应用于http 1.1.