前端缓存

缓存

缓存是一种保存资源副本并在下次请求时直接使用该副本的技术

为什么要缓存(缓存的优点)

  • 缓解服务器压力(不用每次去请求资源)
  • 提升性能,提高访问速度(打开本地资源速度当然比请求回来再打开要快得多)
  • 减少网络 IO 消耗,减少带宽消耗
  • 通过网络获取内容既速度缓慢又开销巨大
  • 如果是较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用

缓存类型

缓存总体可分为:私有缓存(private)与共享缓存(public)

  • 私有缓存:该资源只能被浏览器缓存,private 为默认值,只能用于单独用户
  • 共享缓存:该资源既可以被浏览器缓存,也可以被代理服务器缓存,能够被多个用户使用

按种类分的话可以分为:

  • 数据库缓存
  • 代理服务器缓存
  • 网关缓存(CDN缓存)
  • 浏览器缓存

浏览器缓存机制

我们从三个方面去了解浏览器的缓存机制:

  • 浏览器缓存位置分析
  • HTTP缓存策略(主要)
  • 用户行为对缓存的影响

浏览器缓存位置分析

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

Service Worker

  • Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,可以帮我们实现离线缓存、消息推送和网络代理等功能
  • 使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全
  • Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的
  • Service Workers利用了ES6中比较重要的特性Promise,并且在拦截请求的时候使用的是新的fetch API,之所以使用fetch就是因为fetch返回的是Promise对象
  • Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据

Memory Cache

  • Memory Cache,是指存在内存中的缓存。包括当前中页面中已经抓取到的资源,如页面上已经下载的样式、脚本、图片等
  • 因为存储在内存中,Memory Cache 是响应速度最快的一种缓存,但由于同样的原因,缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
  • Memory Cache那么的高效,是否所有资源都可以放进内存缓存中?那肯定是不行的
  • 计算机内存有限,比硬盘容量小很多,浏览器会考虑计算机具体情况来决定缓存放在内存中还是硬盘中
  • 内存缓存在缓存资源时并不关心返回资源的HTTP缓存头信息,小文件优先缓存在内存中

Disk Cache

  • Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上
  • 会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求

Push Cache

  • Push Cache 是 HTTP2 在 server push 阶段存在的缓存,当以上三种缓存都没有命中时,它才会被使用。
  • Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
  • Push Cache 中的缓存只能被使用一次
  • 可以推送 no-cache 和 no-store 的资源
  • 浏览器可以拒绝接受已经存在的资源推送

HTTP 缓存策略

当客户端向服务器请求资源时,会先抵达浏览器缓存,当以上缓存都没有命中的时候,我们就需要发起请求获取资源了。当然为了性能考虑,我们肯定不希望所有的资源每次都是请求来的,所以需要给不同的资源选择不同的缓存策略
然而常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力
通常浏览器缓存策略分为三种:强缓存协商缓存启发式缓存,并且缓存策略都是通过设置 HTTP Header 来实现的

强缓存

  • 不会向服务器发送请求,直接从缓存中读取资源
  • 状态码:200,显示 from disk cache 或 from memory cache
  • 设置两种 HTTP Header 实现:expires 和 Cache-Control

**expires:**值是一个时间戳,表示本地时间到这个设置的时间缓存就失效。这样一来 expires 就是有问题的,受限于本地时间,如果服务端和客户端的时间设置可能不同,或者我直接手动去把客户端的时间改掉,那么 expires 将无法达到我们的预期。因为 expires 的缺陷,HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务,Cache-Control 优先级高于 expires。继续使用 expires 的唯一目的就是向下兼容

**Cache-Control:**通过 max-age 来控制资源的有效期。max-age 不是一个时间戳,而是一个时间长度,完美地规避了时间戳带来的潜在问题

// 表示缓存30秒后失效,30秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求
Cache-Control: max-age=30

Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令,以下为响应指令:

  • public:响应可被任何中间节点(客户端和代理服务器)缓存
  • private:只有客户端可以缓存,Cache-Control的默认取值
  • max-age=< seconds >:表示缓存内容将在xxx秒后失效
  • s-maxage=< seconds >:同max-age作用一样,只在代理服务器中生效(比如CDN缓存),s-maxage优先级高于max-age,只对 public 缓存有效。设置了 s-maxage,没设置 public,代理服务器也可以缓存这个资。
  • no-cache:可以缓存,但每次都应该去服务器验证缓存是否可用,进入协商缓存阶段。不使用 Cache-Control 的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。相当于max-age:0, must-revalidate
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • must-revalidate:可缓存但必须再向源服务器进确认
  • proxy-revalidate:要求中间缓存服务器对缓存的响应有效性再进行确认

协商缓存

当 expires 和 Cache-Control 过期或者它的属性设置为no-cache时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新

  • 如果服务器端的资源没有修改(Not Modified),那么就会返回304状态码,告诉浏览器可以使用缓存中的数据
  • 如果数据有更新就会返回200状态码,服务器就会返回更新后的资源并且将缓存信息一起返回
  • 跟协商缓存相关的header头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since)请求头和响应头需要成对出现

启发式缓存

如果expires,Cache-Control: max-age或Cache-Control:s-maxage都没有在响应头中出现,并且设置了Last-Modified时,那么浏览器默认会采用一个启发式的算法,即启发式缓存:根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期,这是浏览器默认的缓存方式
在这里插入图片描述
在这里插入图片描述
补充:

prefetch cache是一种浏览器预加载机制,其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到

如何实现?对要预加载的文件的 link 标签加上 rel=“prefetch”

<link rel="prefetch" href="/images/big.jpeg">

强缓存和协商缓存两种缓存方案存在问题

**强缓存:**一般我们会设置Cache-Control的值为“public, max-age=xxx”,表示在xxx秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。显而易见,如果在xxx秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果说你不着急,可以接受这样的,那是不是完美?然而很多时候不是你想的那么简单的,如果发布新版本的时候,后台接口也同步更新了,那就gg了。有缓存的用户还在使用旧接口,而那个接口已经被后台干掉了怎么办?

协商缓存:协商缓存最大的问题就是每次都要向服务器验证一下缓存的有效性,似乎看起来很省事,不管那么多,你都要问一下我是否有效。但是,对于一个有追求的码农,这是不能接受的。每次都去请求服务器,那要缓存还有什么意义

前端缓存最佳实践

缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以最佳实践就应该是尽可能命中强缓存,同时能在更新版本的时候让客户端的缓存失效

在更新版本之后,如何让用户第一时间使用最新的资源文件呢?机智的前端们想出了一个方法,在更新版本的时候,顺便把静态资源的路径改了,这样就相当于第一次访问这些资源,就不会存在缓存的问题了

webpack在打包的时候会在文件的命名上带上hash值

entry:{
 main: path.join(__dirname,'./main.js'),
 vendor: ['react', 'antd']
},
output:{
 path:path.join(__dirname,'./dist'),
 publicPath: '/dist/',
 filname: 'bundle.[chunkhash].js'
}

webpack给我们提供了三种哈希值计算方式,分别是hash、chunkhash和contenthash,那么这三者有什么区别呢?

  • hash:跟整个项目的构建相关,构建生成的文件hash值都是一样的,只要项目里有文件更改,整个项目构建的hash值都会更改
  • chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值
  • contenthash:由文件内容产生的hash值,内容不同产生的contenthash值也不一样

显然我们是不会使用第一种的,改了一个文件,打包之后其他文件的hash都变了,缓存自然都失效了,这不是我们想要的。那chunkhash和contenthash的主要应用场景是什么呢?在实际在项目中,我们一般会把项目中的css都抽离出对应的css文件来加以引用,如果我们使用chunkhash,当我们改了css代码之后,会发现css文件hash值改变的同时,js文件的hash值也会改变。这时候contenthash就派上用场了

综上所述我们可以得出一个较为合理的缓存方案:

  • HTML:使用协商缓存
  • CSS、JS、图片:使用强缓存,文件命名带上hash值

后端设置缓存

强缓存:

res.setHeader('Cache-Control', 'public, max-age=xxx');

协商缓存:

res.setHeader('Cache-Control', 'public, max-age=0');
res.setHeader('Last-Modified', xxx);
res.setHeader('ETag', xxx);

参考文章

前端缓存看这一篇就够了

清理下缓存就好了

页面性能优化:前端缓存最佳实战

深入理解浏览器的缓存机制

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值