前端缓存主要是分为HTTP缓存
和浏览器缓存
。其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置
;而浏览器缓存则主要由前端开发在前端js上进行设置。
http缓存是web缓存的核心,是最难懂的那一部分,也是最重要的那一部分。
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
样式、脚本、图片
等。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
读取速度慢点
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,通常浏览器缓存策略分为两种:强缓存和协商缓存
,并且缓存策略都是通过设置 HTTP Header
来实现的。
对于强制缓存而言,如果浏览器判断所请求的目标资源有效命中,则直接从强制缓存中返回请求响应,无须与服务器进行任何通信。返回200的状态码。
在浏览器控制台NetWork中的体现为:
200 OK (from disk cache)
或者200 OK (from memory cache)
200 OK (from disk cache)
HTTP状态码200,缓存的文件从硬盘中读取200 OK (from memory cache)
HTTP状态码200,缓存的文件从内存中读取强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
3.1基于expires实现
request header
: Last-modified
response header
: expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
cache-control
字段3.2基于cache-control实现
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
Cache-Control:max-age=N,N就是需要缓存的秒数。从第一次请求资源的时候开始,往后N秒内,资源若再次请求,则直接从磁盘(或内存中读取),不与服务器做任何交互。
Cache-control中因为max-age后面的值是一个滑动时间,从服务器第一次返回该资源时开始倒计时。所以也就不需要比对客户端和服务端的时间,解决了Expires所存在的巨大漏洞。
基于cache-control
实现的强缓存是当下项目中的常规方法,而基于expires
实现的强缓存不被推荐使用。
Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令
Cache-control如何设置多个值呢?用逗号分割
Cache-control:max-age=10000,s-maxage=200000,public
如果要考虑向下兼容的话,在Cache-control不支持的时候,还是要使用Expires,这也是我们当前使用的这个属性的唯一理由。
3.3强缓存两种方式的区别
3.4强缓存的缺陷
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
顾名思义,协商缓存就是在使用本地缓存之前,需要向服务器发起一次GET请求,与之协商当前浏览器保存的本地缓存是否已经过期。通常是采用所请求资源的最近一次的修改时间戳来判断的。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效(文件未更新),返回304和Not Modified。
协商缓存失效(文件更新),返回200和请求结果。
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
4.1基于last-modified实现
第一次请求,服务端代码
const data = fs.readFileSync('./imgs/CSS.png');//读取资源
// 1.读取修改的时间
const { mtime } = fs.statSync('./imgs/CSS.png');
// 2.设置文件最后修改时间
res.setHeader('last-modified',mtime.toUTCString())
// 3.强制设置为协商缓存
res.setHeader('Cache-Control','no-cache');
res.end(data);
第二次及以后的每一次请求,服务器端代码
const data = fs.readFileSync('./imgs/CSS.png');//读取资源
const { mtime } = fs.statSync('./imgs/CSS.png');//读取修改的时间
const ifModifiedSince = req.headers['if-modified-since'];//读取请求头携带的时间(第一次返回给客户端的文件修改时间)
if (ifModifiedSince === mtime.toUTCString()) {
// 如果两个时间一致,则文件没被修改过,返回304
res.statusCode = 304;
res.end();//因为缓存生效,不需要返回数据
return;// 避免返回新的last-modified
}
res.setHeader('last-modified',mtime.toUTCString())// 设置文件最后修改时间
res.setHeader('Cache-Control','no-cache');// 强制设置为协商缓存
res.end(data);
流程
last-modified的不足
综合,以上两种不足可知,基于last-modified实现的协商缓存,服务器无法根据资源修改的时间戳识别出真正的更新,进而导致重新发起了请求
4.2基于Etag实现
为了弥补通过时间戳判断的不足,从HTTP1.1规范开始新增了一个Etag的头信息,即实体标签。 其内容主要是服务器为不同的资源进行哈希计算所生成的一个字符串,该字符串类似于文件指纹,只要文件内容编码存在差异,对应的Etag对文件资源进行更精准的变化感知。
也就是说,ETag就是将原先基于last-modified协商缓存的比较时间戳的形式修改成了比较文件指纹。
文件指纹:根据文件内容计算出的唯一哈希值。文件内容一旦改变则指纹改变。
etag的不足
在协商缓存中,Etag并非last-modified的替代方案而是一种补充方案,因为依旧存在一些弊端。
4.3Etag/last-modified对比