前端性能优化优先级最高的便是浏览器缓存策略
提到前端性能优化,有一个问题就怎么也绕不开
那就是,提升首屏加载速度.
这时候,很多人都会率先想到服务端渲染,或者优化构建包这些常用的解决办法.
但是这些办法往往都不是最优解,因为这些方法往往只能有限的提升首屏首次记载的速度,且优化效果往往不是特别明显.
那么我们还有没有更好的方法呢?
肯定是有的呀~
其实浏览器早就为我们提供了一份缓存机制,我们完全可以利用浏览器缓存机制来巨幅提高加载速度~
废话终于说完了,我们直接来看干货.
首先我们来聊一下缓存的优先级:
当客户端需要请求一个服务端资源的时候,会率先检查浏览器中是否有缓存,没有则直接调取接口.
若有,则会检查缓存是否过期,判断是否过期的方法有两种.
- Expires(到期时间)(http1.0规则下的响应头)
如果服务端返回的response header中含有 Expires字段,且这个字段应该是一个时间戳,类似
Expires: Wed, 25 Oct 2019 16:48:00 GMT
如果客户端此次发送请求的时间在Expires之前,则会直接触发缓存,不再去发起请求.
2. Cache-Control(缓存控制)(http1.1规则下的响应头)
Cache-Control同样是服务端返回的一个响应头,他有几个选项可供使用:
我们实际开发中比较常用的一般是public,private和max-age.
尤其是max-age最常出现,举个栗子:
当我们使用诸如webpack之类的前端构建工具时,node_module下的工具包,例如vue,react,lodash等等
这些包都有一个显著的特点,就是我们一旦决定使用,短期内就不会有更新版本的可能了
也就是说,我们没有必要每次都向服务端发送请求,将这些不常变动的包下载下来,那么我们就可以将这些包单独打包,部署至服务端,在服务端配置响应头Cache-Control: max-age=100000;
值得注意的是,如果我们使用这种缓存策略,我们的第三方包就不可以变更名字,因为一旦包的名字发生了变化,包的请求就会变成一个新的请求,不会触发cache-control的缓存了.
而以上两种缓存方式,被总称为强缓存,且Cache-Control的优先级要优于Expires~
其实这个概念蛮好理解的,因为强缓存一旦触发,就不会再向服务端发送请求了,着实是非常的强力~
然而这个时候有好事的同学肯定会问: 那么缓存都是缓存在哪里呀?
这个问题问的很好(qian er),其实浏览器的缓存一般都存放在内存或者磁盘中.
1.内存缓存(memory cache)
一般将脚本,图片,字体等常常和页面产生交互的部分存放在内存中,原因也很简单,比较利于性能提升.
2.磁盘内存(disk cache)
一般将css等这些不经常变动的数据放在磁盘中进行缓存.
然而,既然有了之前比较欠的提问,相信肯定会有更欠的同学会问:
既然内存和磁盘缓存这么厉害,我们要怎么配置呢?
答案也非常简单,就是我们无法配置,这个是浏览器的默认行为,我们要做的就是把它记下来,将来才可以面对面试官郎朗的吹着牛啤~
好像扯的有点远,我们回归正题,之前我们讲了不需要发送请求的强缓存,现在我们再来讲一下更加重要的协商缓存,也就是需要和服务端交互的缓存机制.
协商缓存
顾名思义,协商缓存就是客户端在没有匹配到强缓存的前提下,向服务端发起了请求,而服务端则会使用两种方式来判断.
请求的资源是否在上一次请求和这一次请求之间发生过变化.
如果发生了变化则正常发起200响应,反之则发起304响应,直接触发协商缓存.
这两种方法分别是:
- Last Modified 与 If-Modified-Since(http1.0响应头和请求头)
服务端在上一次响应客户端响应时,可能会返回一个Lase Modified的响应头,对应的值是一个时间戳,例如:
Last-Modified: Wed, 25 Oct 2019 16:48:00 GMT
这个字段对应的时间代表服务端该资源的最后一次更改时间,当客户端再一次请求该资源时,浏览器会自动为请求添加If-Modified-Since请求头,且该请求头携带上一次Last-Modified的值.
服务端接收到If-Modified-Since请求头后,会和服务端所储存的该资源最后修改时间作对比,如果没有任何变化,服务端会响应304,客户端就会直接从缓存获取数据.
如果不相等,则说明两次请求间服务端已经修改过该资源,则会响应200,并重新返回数据传输给客户端.
值得一提的是,如果我们并没有配置缓存策略,浏览器会将响应头中的Date减去Last-Modified再乘以0.1,作为我们的资源缓存时间.
但是Last Modified&If-Modified-Since组合由于是非常老的http1.0的规则,也是有着比较显著的缺点的:
这种方法侦测改变的时间的最小单位为1s,这意味着如果在1s的时间内,请求的资源发生了改变,也会正常触发缓存,导致客户端无法获取到最新资源.
当然,我们也有更好的方式来处理这个问题那就是下面要介绍的Etag.
2. Etag与If-None-Match(http1.1响应头和请求头)
Etag是服务端根据请求资源的内容所生成的一种类似于Token的标识,而下次发起对该资源的请求时,请求头会携带If-None-Match字段,该字段携带着上次服务端返回的Etag的值.
服务端接收到If-None-Match之后,会和该资源的标识进行对比,如果相同则认为资源未发生变化,响应304,客户端自动使用资源缓存
如果发现两次的标识不相同,则会响应200,且发送最新的资源.
Etag&If-None-Match的缓存策略优先级要比Last-Modify&If-Modified-Since高,且缓存精度也更高,不会出现Last-Modified&If-Modified-Since在1s中内失去效应的情况.
但是,由于Etag需要服务端使用特定算法判断资源变化情况,所以所占用资源较多,性能上不如Last-Modified&If-Modified-Since这种判断时间戳的方式快.
不过从目前的开发形式来看,我还是更推荐使用Etag,一方面如今的服务端性能大大提高.
另一方面,现在大多数服务端都采用了负载均衡策略,可能导致不同虚拟主机返回的Last-Modified时间戳不一致,导致对比失败~
如果想了解更多关于http的知识,可以去看下MDN的讲解,如果有同学想要尝试,建议使用node.js搭建一个本地服务器,最近我对node也越来越感兴趣,后面会和大家多分享node的一些知识.
最后,附一张图,便于大家调试浏览器缓存~