今天主要分享几点性能优化的实用方案,在前几个月在公司实习的时候,通过回顾做的一些实战项目总结出来了的,这对于刚刚接触到前端性能问题的童鞋,希望能够给你提供一些入门型的指导。
谈起性能优化这个话题,实在是太大的,不仅仅只是前端的性能优化,也是全链路的性能优化。前端、后台、服务器、网络等各个模块的配合。
我在搞性能优化中,有些有关性能优化的点是《前端性能优化》的作者阿里的修言老哥给我稍作指点,加上实战中的应用和反馈总结,才让我得以有所感悟,值的去写一写。
今天所分享到的性能优化这个范围也是根据我之前公司所在的一些业务和所使用的技术栈来展开聊聊的。有具体的优化场景,我们再谈起性能优化才觉得更加有意思。
面临的技术栈和场景
如果不是对技术栈要求特别严格的公司来说,前端开发少不了这几个技术栈。HTML5 + CSS3 + jQuery,有些公司还在用 JSP、.Net开发,针对这写传统技术,我们需要使用一些特殊的手段去做优化。
脱离了像 Vue 这种本身就是放弃传统操作 DOM 的框架,只针对于传统的性能优化,我们如何下手?
PS:本文不谈太过关于框架性能优化的内容,只从传统的性能优化点出发。
从定位问题开始
有一次,线上项目出现问题,页面某一部分渲染需要很长时间才显示,到底是哪出了问题。

定位问题,是什么导致了该页面部分内容加载过慢,是数据的获取过程,还是页面 JS 加载过程,还是其他渲染、绘制过程?
通过浏览器控制台的俗称神器的 Performance 定位问题。
这里我以某网站为项目对象,做个示范。
那个部分用时比较长,通过这个图可以分析到是哪个环节出现了渲染时间比较长。
定位问题之后,开始做优化。但是做优化前提是具体一些优化的基础内容,不然我们根本不知道从哪里着手开始优化。
数据请求时间过长
有时候我们在做项目时,数据量太大,从服务器拿到数据这个过程时间太长。要想将这个时间变短,我们需要减少请求的次数以及单次请求的时间。
对于减少请求次数,我们能一次获取的尽量一次获取到数据,而不是分为多次去请求,如果请求过多就会造成线程的阻塞。
我们可以结合具体业务进行适当的优化,将一些返回的【数据合并】,从而达到减少请求次数的目的。
但是单次请求的时间过长也是耗时间的,我们传输大量的数据,只能将这些数据做压缩处理,这样单次请求就会变快。
还有其他一些方案,比如懒加载,页面给用户呈现哪部分内容,就请求哪部分内容,而不是一股子气全部请求回来。再比如,音频走的是流文件,而不是把整段音频请求回来才给用户播放,而是以流水的形式,边播放,边请求。
避免发生重绘和回流
除此之外,最关键的就是页面中的优化。首先回顾一下浏览器的工作原理。
动画: 一个浏览器是如何工作的?
其中一个环节是渲染环节,将合成的渲染树通过计算得出的像素位置和样式,渲染到页面中,最后呈献给用户。
虽然我们在浏览器看到的页面没有感觉,但是这个过程非常耗时的,尤其是当我们稍微改动页面某一个元素的位置大小或者样式的时候,都会导致整个页面重新进行渲染。
如果页面要渲染的内容很多,用户每改动一个地方,就会造成全部渲染,性能非常耗时,最后导致用体验不好,严重情况造成页面无响应。
我们看到的很多文章中提到,我们应该尽量的避免频繁的操作 DOM,从而避免发生页面的回流和重绘。
我们发现 Vue 做了哪些改进,既然操作 DOM 导致性能很差,我就不操作 DOM 呗,所以 Vue 放弃了传统的 DOM 操作,开启了数据影响视图模式。(PS:后期在分析 Vue 源码文章中,我会主要分享这部分它内部如何巧妙实现的,然后带大家自己手写一个简易版的 Vue)
但是很无奈,有些公司不用 Vue,这样的公司太多了,领导表示就是不想用。就是想用传统的 HTML5 + CSS3 + jQuery,那么我们对于优化又该怎么做?
我个人又根据,做了一些性能优化总结。既然不让用 Vue,那就决定的特定场景还是涉及到操作 DOM 咯,虽然不能放弃传统 DOM,那么,我尽量的减少操作 DOM 行不行,没错,按照这个方向去想,我们可以总结出以下几点内容,小本本划重点。
1、
分离读写操作
我们频繁的改变页面样式的位置以及大小会导致页面的回流和重绘,当代浏览器对这一点做了一点优化,当前行代码如果改变了元素大小或者位置,不会立即导致页面重新渲染,而且判断下一行是否也是改变元素大小和样式。
div.style.background = '#fff'
div.style.color = 'red'
如果是,这些连续的代码将阿计入到一个渲染队列当中去,直到下一行代码不是改变样式的代码,然后统一做一次页面渲染,这样大大减少了页面的回流和重绘。
我们根据这个浏览器渲染队列机制,我们在写代码的时候,将读写属性操作做分离,像下面这样。
var curLeft = div.offsetLeft;
var curTop = div.offsetTop ;
div.style.left = curLeft + 1 + 'px'
div.style.top = curTop + 1 + 'px'
如果不分离读写操作,而是将读写操作混起来写,这样会导致不断的回流和重绘。
2、
样式集中改变
同理,在我们用 JS 批量改变样式的时候,要进行集中改变样式。
box.style.cssText = 'width:200px;height:200px';
box.className = 'aa'
3、
缓存布局信息
我们通常会通过 JS 动态布局的大小,我们通常会这样做。
// 以下会引发两次回流
div.style.left = div.offsetLeft + 1 + 'px'
div.style.top= div.offsetTop + 1 + 'px'
但是按照我们上边所总结的,这样子做不好,因为会导致多次重绘。
如果我们这样,先把获取的布局信息通过变量进行缓存,然后再统一改变样式。
// 修改为:只会触发一次回流
var curLeft = div.offsetLeft;
var curTop = div.offsetTop ;
div.style.left = curLeft + 1 + 'px'
div.style.top = curTop + 1 + 'px'
4、
元素批量修改
如果我们进行批量的改变元素,我们可以单独拿出来改变完,然后再放回页面中,这样,你无论改变多少次,只引发一次回流。
// 文档碎片
let frg = document.createDocumentFragment();
for(let i = 0;i < 5;i++){
let newLi = document.createElement('li');
newLi.innerHTML = i;
frg.appendChild(newLi);
}
除此之外,我们也可以使用字符窗拼接的方式。
// 字符串拼接
for(let i= 0;i< 5;i++){
str += `<li>${i}</li>`
}
box.innerHTML = str
5、
动画优化
有时候我们想使用动画,动画会频繁的改变元素的状态,导致不断的回流和重绘。
所以我们为了能够使性能更佳,我们动画效果应用到 position 属性为 absolute 或 fixed 上(脱离文档流)。
虽然会在一个新的平面,也会引发回流,但是不会影响到其他的元素,这样看起来性能更好。
6、
开启 CSS 硬件加速(GPU加速)
CSS3 改变样式能用 transform 的局部用其他元素,因为 transform 开启了硬件加速,规避了【回流】和【重绘】。
box.style.transform = 'translateX(200px)'
硬件加速的其他属性:transform \ opacity \ filters。所以能用这些属性尽量用这些属性去改变元素。
7、
牺牲平滑速度换取速度
每次动画移动一个像素,CPU 占用率 100%,动画看起来有些跳动,因为浏览器正在与回流作斗争。
如果每次移动 3 像素可能看起来平滑度降低了,但是不会导致 CPU 在较慢的机器中抖动。
小结
以上是对于一些传统网页存在的性能问题,总结的几点优化方案。但是只靠这几点还是远远不够的。
开篇提到过,性能优化不仅仅是前端或者是后端工程师的事情,而且全链路的性能优化。
无论是性能优化还是算法、网络等其他方面,都应该形成一种属于自己的思想知识体系。通过不断的遇到问题,解决问题,然后不断完善这个体系,这才是我们最终要追求的一个小目标。