性能优化
Opened this issue · 0 comments
性能优化
在互联网,速度是关键。很多案例已经证明:
- 网站越快,用户的黏性越高
- 网站越快,用户忠诚度越高
- 网站越快,用户转化率越高
在前端领域,性能优化有两个主要目的:
- 加载要快,包括静态资源下载速度、服务器响应动态资源的速度
- 响应要顺畅,用户的操作要即时响应,并且给予提示
优化网络请求
目前,一个普通的Web应用大约就有1MB,有100个左右的资源分散在15台不同的主机上。优化网络请求势在必行。
延迟和带宽
对于网络请求,有两个指标指示请求的质量:延迟和带宽。
延迟,分组从信息源发送到目的地所需的时间
带宽,逻辑或物理通信路径最大的吞吐量
客户端到服务器的总延迟时间包括:
- 传播延迟,消息从发送端到接收端需要的时间,是信号传播距离和速度的函数,影响延迟的主要元素
- 传输延迟,把消息中的所有比特转移到链路中所需的时间,是消息长度和链路速度的函数
- 处理延迟,处理分组首部、检查位错误及确定分组目标所需的时间,这部分时间主要是花费在路由器里
- 排队延迟,到来的分组排队等待处理的时间
对于带宽,目前小区带宽普遍百兆光纤,4G移动网络,就上下行速率来说,可以看成是一个百兆级的宽带,现在比较普遍的100M宽带的下行是100Mbps,上行是20Mbps。
那么延迟和带宽,哪个才是影响网络体验的关键呢?
可以看看下图,某网址首页加载html静态资源的Timing图:
蓝色部分是下载数据需要的时间,它是受带宽影响的;其它部分都是延迟影响的,特别是绿色部分(Waiting),它是受传播延迟影响的,橙色部分是建立TCP连接花费的时间,紫色部分是其中建立SSL花费的世界。很显然,在现在互联网时代,带宽不再是网络体验的瓶颈,延迟才是。
TTFT,Time To First Byte,接收到第一个字节所需要的时间
如何降低延迟
让我们理清资源交互过程。一次请求过程,包括建立TCP、TLS,然后是发送HTTP报文,等待HTTP响应。
对于TCP
建立TCP连接需要三次握手,那么就相当于放大三倍延迟,所以提高TCP应用性能的关键,在于想办法重用连接,当然,如果能从逻辑上减少握手的步骤也是一种策略,TFO就是其中一种策略。
TFO(TCP Fast Open,TCP快速打开)通过握手开始时的SYN包中的TFO cookie(一个TCP选项)来验证一个之前连接过的客户端。如果验证成功,它可以在三次握手最终的ACK包收到之前就开始发送数据,这样便跳过了一个绕路的行为,更在传输开始时就降低了延迟。
重用TCP连接,目前已经广泛使用在HTTP1.1协议,保持长连接(Connection: keep-alive)便可以重用TCP连接,减少建立TCP花费的世界。
当然,如果使用UDP替代TCP将会彻底减少建立连接需要花费的世界,QUIC(快速UDP网络连接)就是基于这个原理。
在QUIC广泛应用之前,我们可以使用HTTP2达到最大程度重用TCP连接的目的。HTTP1.1时代为了加速网络访问,浏览器会为每个域名同时建立最多6个TCP连接用以摆脱HTTP队首阻塞。HTTP2是基于帧的,可以多路复用TCP连接,同域名的多个请求可以都重用TCP连接,减少建立TCP连接的耗时。
对于SSL/TLS
SSL/TLS的握手过程也是比较复杂的。大致过程就是,确定加密套件,校验服务器公钥,生成3个随机数,通过三个随机数生成对称加密密钥,校验密钥,传输加密信息。
为了减少握手的耗时。如果某次握手过程的 Client Hello 消息里还附带了上一次的 Session ID,服务端接收到这个 Session ID 并校验后如果能复用密钥就不再进行后续的握手过程。
ALPN(应用层协议协商)作为TLS扩展,能过在TLS握手的同事协商应用协议,从而可以省掉HTTP的Upgrade机制所需的额外往返时间。
静态资源
延迟是跟距离相关的,原则上说,距离越短延迟越低,CDN服务就是基于这个原理。
把静态资源放在CDN服务上,可以有效降低静态资源请求的延迟。
CDN的核心是智能调度,它的底层实现原理依赖运营商,因为运营商有用户IP地址的元信息,还知道哪些地区有哪些IP可以提供服务。所以,在域名解析阶段去分析用户IP,返回最近最佳的服务器IP。
除了CDN,使用缓存可以通过减少请求次数达到减少延迟的效果。
HTTP协议
对于HTTP1.X而已,优化是基于TCP协议特性的,具体策略是减少建立连接次数、减少请求数量、提升并发下载能力:
- 建立持久连接,复用TCP连接,HTTP1.1默认保持长连接
- 使用HTTP管道,把请求提前传到服务器,但还是会有队首阻塞
- 多个TCP连接并行加载资源,浏览器会为一个域名最多建立6个TCP连接
- 域名分区,为了突破一个域名最多建立6个TCP连接而提升并行下载资源的能力
- 连接与拼合,把多个JS或CSS文件组合成一个文件,把多张图片组合为一张更大的复合的图片,有一个缺点就是合并的资源里有一个失效了,会导致其它资源缓存失效
- 小段JS和CSS代码可以直接嵌入页面,小图片也可以使用base64嵌入网页,用于减少请求次数
HTTP管道,原理是把FIFO队列从客户端(请求队列)迁移到服务器(响应队列)
对于HTTP2可以有效解决HTTP1.X遇到的问题,HTTP管道不会再发生队首阻塞,多路复用不需要建立多个TCP连接和域名分区,可以有效缓存小块资源不需要连接与拼合。
优化用户体验
良好的用户体验可以降低用户对延迟的敏感度。
浏览器优化
现代浏览器可以为我们做很多的优化:
- 基于文档的优化
- 资源预取和排定优先次序,发现和优先安排关键网络资源,今早分派请求并取得页面,使其尽快达到可交互的状态
- 推测性优化
- DNS预解析,可以通过学习导航历史、用户的鼠标悬停,或其它页面信号来触发
- TCP预连接,DNS解析之后,浏览器可以根据预测的HTTP请求,推测性地打开TCP连接
- 页面预渲染,在隐藏的标签页中预先渲染整个页面
现代浏览器的标签可以指示浏览器提前记载资源,它的rel属性有如下值:dns-prefetch、prefetch、prerender,分别指示浏览器预解析特定的域名、预取得将来导航要用的资源、根据对用户下一个目标的预测,预渲染特定页面
图片优化
前端性能优化最重要的部分是图片优化。很多网站,图片资源的大小比JS+CSS还大。
图片优化主要有以下手段:
- 懒加载图片,懒加载作为图片优化的主力军广泛适用各种场景,对于比较长的页面,用户不一定每次都会下滑到底部,那么懒加载便能节省下不需要展示的图片的带宽
- 预加载图片,为了让图片今早显示在用户面前,预加载可以通过提前加载图片资源,当真正要去拉取图片资源的时候便可以在缓存中找到
- 响应式图片,把图片裁剪出不同尺寸的图片,供不同的场景使用
动画优化
尽量使用硬件加速,下面三个style属性会使用GPU处理:
- transform
- opacity
- filter
现代浏览器还支持使用 will-change 或 translateZ 把元素提升到一个独立的层。
如果一定要使用JS接口,请使用requestAnimationFrame(callback)
接口,callback的回调带有一个参数——时间戳,值跟performance.now()
的返回值相同,可以借助这个时间戳计算动画量。
交互优化
浏览器上有一些事件是会持续发生的,比如滚动事件、拖拽事件、resize事件等等,这些事件会在每一帧开始前执行事件回调。
如果不是在这些事件上驱动动画的话,建议使用防抖节流的策略去处理事件。
防抖是将多次执行变为最后一次执行。
节流是将多次执行变为在规定时间内只执行一次。
可以通过下图看函数正常执行、防抖、节流的区别:
防抖适合的业务:
非立即执行版:input搜索框,客户输完过一会就会自动搜索;window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次;立即执行版:就是对于按钮防点击,例如点赞、心标、收藏等有立即反馈的按钮。
节流适合的业务:
鼠标不断点击触发,点击事件在规定时间内只触发一次(单位时间内只触发一次);监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。
DOM接口
DOM是浏览器提供的描述文档元素的对象。
通过选择器去查询dom元素是比较耗时的,最好的做法就是用变量保存它。
关于回流和重绘,每当我们去修改一个dom元素的样式(包括几何尺寸和颜色),这个元素会被标记为dirty
,并把更新操作(Recalc Styles、Layout【如果是重绘则跳过】、Update Layer Tree、Paint、Composite)放入下一帧更新队列里,浏览器会在合适的时机去更新dom元素。
几何尺寸包括:width、height、padding、margin、left、top、border等
颜色指背景色、字体色等等
如果此时访问元素的几何、定位属性,如offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight,就会触发强制回流、重绘,元素的dom.getBoundingClientRect();
方法也会触发。
观察如下的代码,和它们的执行过程火焰图:
function test1() {
var $0 = document.querySelector("#sidebar")
$0.style.width = "800px";
}
setTimeout(test1);
观察函数执行过程:
function test1() {
var $0 = document.querySelector("#sidebar")
$0.style.width = "800px";
$0.getBoundingClientRect();
}
setTimeout(test1);
对比可以看出来,第二张图在函数test1里执行强制回流重绘,导致test1执行时间变长,这是兵家大忌。
使用3D加速的CSS属性之所以很快且流畅,是因为动画属性一经提交到GPU就不会再占用主线程了,也就是说该动画的整个过程不会触发Recalc Styles、Layout、Update Layer Tree、Paint、Composite。
参考
《Web性能权威指南》
《前端性能优化原理与实践》,掘金