本文主题:理清浏览器的缓存机制的内部逻辑,并给出避免浏览器缓存的相关解决方案
相信很多新手前端发布页面的时候都会遇到一个问题,就是明明页面已经更新了,但是浏览器浏览页面并没有变化,那么如何解决这个问题呢?
事实上,这个问题各种搜索引擎搜索之后会发现有很多的方案,但不一定有效,一般的解决方案有以下的两种:1:添加时间戳;2:cache-control。
首先第一种,就是在你的所有静态资源文件后面添加随机时间戳,例如你的页面里面用到了test.js,那你修改过test.js在html页面中的引用就要改成像下面这个样子
每次修改test.js之后修改version后面的时间戳,这样浏览器就会忽略缓存从服务器请求新的文件,但是,真正这么做了之后,还是会发现,即使所有修改过的文件在应用的时候都添加了时间戳了,但是页面缓存还是没有清除,这又是为什么呢?原因很简单,你只对你修改过的文件添加了时间戳,但是html页面本身在这个时候已经被修改了,html页面是所有静态资源的载体,如果不对它加上时间戳,所有的其他应用都会沿用旧的缓存,所以,这个时候要让缓存失效,只需要在你的访问的网址上面再添加一个时间戳,例如:http://www.test.com/index.html?version=123456。
但很明显,这种做法其实很不优雅,例如网站的访问地址是不能经常变更的,所以这种方法其实使用收到很大程度的限制,接下来是第二种方法,cache-control。
网上很多教程都会写在meta标签上添加cache-control,大概像下面这个样子
但是,这样做,一点卵用都没有,这样完全没办法避免浏览器的缓存,添加cache-control没错,但是需要在响应头添加,我们都知道,客户端跟服务端的交互用的都是http协议,由服务端回传给客户端的数据我们称之为响应数据,分为响应头(Response Headers)和响应体(Response Body),响应头一般用于指导浏览器以什么样的方式呈现数据,例如编码,解码,压缩,请求能否跨域等操作,Cache-Control是其中的一个,用来指导浏览器如何管理缓存,下面我们详细说一下如何浏览器的缓存机制,然后再说说如何通过响应头来控制浏览器的缓存,我们首先来看一张流程图
这张图片是浏览器访问一个有缓存的页面的时候的决策流程,大体上的流程是这样子的,当你访问一个以前访问过的页面,浏览器会先检查本地缓存是否过期,如果未过期,则直接访问本地缓存,不再从服务器端获取,此时的状态码为200(from cache)如已经过期,则会发送一个http请求到服务端,检查两个标识中的其中一个,Etag或者Last-Modified,向浏览器询问资源是否过期,如未过期,则浏览器会返回一个304状态码,这是浏览器就不会重新下载文件,依旧沿用缓存的内容,但如果此时服务器检测到资源已经过期了,那么就会返回200状态码,并在响应体中返回最新的资源,覆盖缓存,这样浏览器就能拿到最新的资源了。那么Etag和Last-Modified这两个字段代表什么呢?Etag是服务端对不同的文件通过固定的算法生成的一个唯一的hash,当文件被修改时,这个唯一的hash就会发生变化,Last-Modified这个从字面上理解就可以了,他存放的是文件最后的修改时间,这两个都能用来判断当前的文件是否发生了变化。
那么接下来我们要做的就是如何通过响应头来指导浏览器控制缓存的时间和什么时候发送询问请求询问资源是否过期,这里会涉及到两个响应头
1.Expires
Expires用于指导浏览器缓存文件的时间,具体格式大概是这个样子:Expires:Cache-Control
响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires
头会被忽略
2.Cache-Control
简单来说,Cache-Control知道浏览器何时向浏览器确认当前资源是否已过期,他有以下选项Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=
Cache-control: s-maxage=
以下是对指令的详细解释
must-revalidate
缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
public
表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
private
表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。
no-cache
在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证
max-age=
设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。
s-maxage=
覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容。
no-transform
不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-Type等HTTP头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。 no-transform指令不允许这样做。
其中,默认的是private,通常配置比较常用的是no-cache,no-store,max-age,no-cache表示浏览器每次都必须发送查询请求,询问当前资源是否过期,如果过期,则返回304,否则放回新资源,no-store则不发生询问,直接获取最新的资源,max-age与expires不同,他的时间是相对于请求的时间,会覆盖expires中的设定
所以到了这里,读者应该大体上知道要怎么控制缓存了,比较明智的做法是expires跟cache-control进行搭配使用,根据项目本身的更新周期,例如一周内不会更新,那么expires可以配置为一周后的日期,那么这段时间内,访问网页的时候,都不会发生http请求,都会直接读取浏览器的缓存,这样响应速度将会大大提高。cache-control可以设置为no-cache,这样在资源过期之后,浏览器每次加载资源的时候会询问服务器当前资源是否过期,非过期资源将会继续沿用缓存,虽然发生了http请求,但是不用重新下载数据,这样性能依旧可以保持相对较高的水准。
这个时候,有可能有人会发问,响应头要怎么添加,都说了叫响应头了,那就是服务端的事了,这部分前端是无法控制的,这个需要在服务器或者各种代理服务器上进行配置,这个写下来又是一篇长长的文章,这里就不再赘述了
参考资料:
Cache-Control
http缓存
Expires