1. 为什么需要浏览器缓存?
对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。
使用浏览器缓存,有以下优点:
- 减少了服务器的负担,提高了网站的性能
- 加快了客户端网页的加载速度
- 减少了多余网络数据传输
2. 浏览器缓存规则
想要完成上述步骤,就需要浏览器的强缓存和协商缓存共同协作完成。
2.1 强缓存
强缓存指的就是请求的资源在缓存中没有过期时,不与服务器进行交互,直接使用缓存中的数据,否咋就请求服务器的数据。
那么关键问题来了,强缓存是怎么判定缓存中的资源是否过期呢?下面来看一下强缓存的规则:
资源是否过期的关键在于Response Headers中的cache-control字段,这是HTTP1.1才有的字段,在HTTP1.0时使用的是Expire是字段,现在主要用来兼容HTTP1.0。
我们看到图中maz-age = 31656000 这指的就是过期时间,它是以秒为单位的。由于这个是相对时间,所以不会受到服务端和本地时间不统一造成的缓存问题。315360000秒是一年,现在大多网站的静态资源设置的跨度都是315360000秒,也就是一年的时间跨度。
Cache-Control可设置的字段值有很多,下面逐一介绍:
-
public:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用max-age=来精确控制; -
private:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存; -
no-cache:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源; -
no-store:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源; -
max-age=:设置缓存的最大有效期,单位为秒; -
s-maxage=:优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头; -
max-stale[=]:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。
注意,规则可以同时使用多个,这里面最重要的就是max-age 和 no-cache,如果值为max-age=xxx就命中缓存,如果值为no-cache就没有命中缓存,继续走协商缓存。
no-cache和no-store很容易混淆:
- no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存;
- no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。
补充:
Expires是HTTP1.0版本中强制缓存策略的关键字段,通过指定一个具体的绝对时间值作为缓存资源的过期时间,具体的设置方法如下:
我们在首次发起请求时,服务端会在Response Header当中设置expires字段。那么如果在这个时间之前我们发起请求去请求资源,就不会发起新的请求,直接使用本地已经缓存好的资源。
但是它存在一个问题,这里设置的时间和服务端的时间是保持一致的,可是我们最终是用本地时间和expires设置的时间进行比较。如果服务端的时间和我们本地的时间存在误差,那么缓存这个时候很容易就失去了效果,因此功能更强大的Cache-Control就出现了。
2.2 协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
-
max-age=xxx过期了 - 值为
no-store
当然,协商缓存样式依靠Response Headers中的字段完成的。
(1) Last-Modified / If-Modified-Since
Last-Modified指的是最后一次的修改时间,设置方法和强缓存的设置方法一样,都是设置一个时间戳,同样它也是由服务端放到Response Headers返回给客户端。
如果有设置协商缓存,在首次请求的时候,返回的Response Headers会带有Last-Modified。当再次请求没有命中强缓存的话,这个时候Request Headers就会携带If-Modified-Since字段,它的值就是第一次请求返回的Last-Modified值。
服务端接收到资源请求之后,根据If-Modified-Since的字段值和服务端资源最后的修改时间是否一致来判断资源是否有修改。如果没有修改,则返回的状态码为304;如果有修改,则返回新的资源,状态码为200。
状态码304并不是一种错误,而是告诉客户端有缓存,直接使用缓存中的数据。返回页面的只有这个头部,是没有内容部分的,这样在一定程度上提高了网页的性能。
这个字段有一定的缺点:
- 服务端会定期生成一些文件,有时候文件内容并没有发生变化,但是这时候
Last-Modified会发生改变,导致无法使用缓存。 - 服务端对
Last-Modified标注的最后修改时间只能精确到秒,如果某些文件在1秒内被修改多次的话,这时服务端无法准确标注文件的修改时间。
(2)Etag / If-None-Match
我们可以看到上看字段有它的缺陷,所以又增加了新的字段Etag / If-None-Match,用法与Last-Modified/If-Modified-Since相似,但是Etag更准确。它通常是根据文件的具体内容计算出一个hash值,只要文件的内容不变,它就不会发生改变,保证了唯一性。
如果有设置协商缓存,在首次请求的时候,返回的Response Headers会带有Etag值。当再次请求没有命中强制缓存的时候,这个时候Request Headers就会携带If-None-Match字段,它的值就是第一次请求返回给我们的Etag值。服务端再用Etag来进行比较,如果相同就直接使用缓存,如果不同再从服务端拉取新的资源。
所以协商缓存的步骤就是:
- 在请求资源时,将用户本地资源的Etag值带到服务端,和服务端的资源进行对比
- 如果资源更新了,返回状态码200,返回新的资源
- 如果资源没更新,返回状态码304,直接读取本地缓存的资源
3. 总结
浏览器的缓存规则如下图:
最后,在加上一个面试题吧,last-modified、 etag、cache-control的优先级?
优先级为: cache-control > etag >last-modified