You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Better, only one layout.varnewWidth=aDiv.offsetWidth+10;// ReadvarnewHeight=aDiv.offsetHeight+10;// ReadaDiv.style.width=newWidth+'px';// WriteaDiv.style.height=newHeight+'px';// Write
首先需要注意的是,不要进行微优化。就是不要一边写一边想着如何优化,要等到业务实现之后再去测试,如果有性能瓶颈,再去考虑如何优化。
上文介绍了渲染的五个步骤:
其实这只是重新渲染的一种情况,并不是每次重新渲染都需要完整的经过这五个步骤。其他的两种重新渲染的情况如下:
下面就依次说明每一个步骤的优化方式。
优化JavaScript
优化JavaScript本身运行的性能,比如对比一个业务是用for循环更快还是用while循环更快。这是有点麻烦的地方,容易陷入到微优化的怪圈中,浏览器运行JavaScript代码的速度越来越快了,很多时候花了大力气研究了性能,浏览器更新升级之后,优势可能就不存在了。所以建议最后考虑这种优化方式。
比上面的方式更划算的方式是Web Worker。这应该算是这几年各大浏览器的重大更新了,它相当于给JavaScript增加了线程,可以在后台做一些繁重的计算工作,但这并不会影响页面的渲染。
Web Worker的使用方法:
Worker.js:
经过实践,这种方式是可行的,不过需要注意使用方式。虽然是第二个线程,可以取回数据终究还是异步的,这个异步的过程的时间是无法确定的,所以如果需要用web worker做计算的话,最好尽早交给他处理,把处理好的数据放在那里,用的时候再直接取值。如果用的时候才调用web worker的方法,计算本身消耗的时间可能不多,可是异步的过程时间可能会很长。
优化Style
不要使用过于复杂的选择器。
例如:
这种选择器给浏览器带来的压力就会有点大。最好是改成class,触发事件的时候直接把新的class替换到所需的元素上。
完整的例子在Box_Style_Change。
修改前的性能测试结果:
修改后的性能测试结果:
重排与重绘
这几乎可以说是优化渲染过程最重要的部分了,尤其是在对动画做优化的时候,重排和重绘是要重点考虑的。
重排就是重复渲染过程中布局的步骤,而重绘就是重复绘制的步骤。可能会导致重新渲染的操作有:修改DOM、修改样式表、用户事件(鼠标悬停、滚动等)。
这些操作中,有的会触发重排,有的会触发重绘,总的来说,如果修改了DOM节点的大小或位置,是会触发重排的;如果只是修改了背景色、字体颜色等,不会触发重排,仅仅会触发重绘;transform和opacity既不会触发重排也不会触发重绘,重新渲染只有合成的步骤。
具体的DOM操作和CSS属性对重排和重绘的影响可以看How (not) to trigger a layout in WebKit和csstriggers。
我们在写代码的过程中,需要重点考虑如何避免多次触发重排和重绘。
虽然设置元素节点的宽高和位置会导致重排,可为了更好的性能,浏览器会有优化。比如上面的两行代码,第一行设置了宽度,浏览器并不会立刻重排,因为下一行还是一个会触发重排的操作,浏览器会把这些设置集合到一起,最后之进行一次重排。
可如果写成下面这样:
由于第二行代码是一个读取的操作,这时如果第一行的设置还没有反应到页面上的话,读取来的数据就会不准确,所以,浏览器就不得不触发重排。
对于DOM操作,比较好的写法应该是这样的:
这种思路的优势在动画的优化上就会显得非常的明显。在前端性能优化之渲染之概述中说过,一个动画实际上就是浏览器在不断的重复渲染出新的图形的过程,所以,一个动画会经过非常多次的渲染过程。
这样一行伪代码就非常可怕了,一次循环就要重新渲染两次,循环1000次,那么一共就渲染了2000次。
性能测试的结果是这样的:
放大来看,就会发现其中发生了非常多次的布局:
真实的业务逻辑会比这个复杂,在一次循环中可能会触发的渲染会更多。这个开销就非常大了。解决方案,可以尝试写成两个循环:
性能测试的结果:
效果还是显而易见的。
关于重排和重绘,我还准备了一个完整的例子,是从udacity的优化课程的一个例子中修改过来的,其中有用到
requestAnimationFrame
这个API。动画都应该优先考虑使用requestAnimationFrame
来实现,而不是setTimeout
或setInterval
。requestAnimationFrame
会让你的JavaScript代码在渲染的五个步骤中尽可能早的时间完成,有时候渲染可能已经进行到重排了,但是一部分代码才开始执行,这是由于setTimeout
和setInterval
无法精确的控制时间,进而导致重新开始整个渲染过程。可如果你的代码写成了这样:
requestAnimationFrame
也是救不了你的。合成
也就是渲染的最后一步,用来合成各个图层。这个部分可以用来帮助优化重绘。比如在做点击按钮左侧菜单动画出现的效果。左侧的菜单基本上是不变的,那么就可以把左侧的菜单单独放到一个图层上面,这样在每次重新渲染的时候就不会重绘这个部分了,只需要在最后合成的时候把所有的图层合并在一起就可以了。
把左侧菜单放到一个新图层的方式是用css的
will-change
属性。具体例子见Will_Change。修改前:
帧数非常低。
修改后:
可见对于这个事例来说,只加了一个
will-change
,前后的性能差别巨大。如果浏览器不支持
will-change
也可以用transform: translateZ(0)
,效果是一样的。这两个属性都会告诉浏览器将要发生什么,浏览器就会提前做准备,把指定的部分放到一个单独的图层中去。使用这种方式要注意滥用的问题:
一个通配符,给很多的元素都加上了这个属性,浏览器不得不建立非常多的图层来处理,而图层本身也是会占用内存的。
合成与绘制是需要权衡的,使用了图层,可能会减轻绘制的压力,可合成的压力却增加了。所以就需要看用那种方式更划算了,具体量化的方式当然就是用浏览器的性能测试工具去做测试了。
总结一下这一篇的内容
再重复一边本文开头的话:不要进行微优化。如果发现有性能问题,先用浏览器提供的工具进行分析,按照分析的结果对有瓶颈的地方做针对性的优化。
这一篇的优化方式,总得来说就是减少重新渲染的次数。前端性能优化之渲染之概述中说的页面生命周期的四个步骤都可以尝试用这个思路进行优化。只有Web Worker的思路会略有区别。在这四个步骤中,动画是重点,一般也是比较困难的地方。
优化动画的思路:
如果可以,尽量使用CSS的方式来实现动画的效果,如
transform
;尽量不要使用过于复杂的CSS选择器;
减少重排和重绘的次数;
能用
requestAnimationFrame
就不要用setTimeout
之类的API;如果有变化不大的部分,可以尝试用
will-change
把这个部分放到一个新的图层中去。参考资料:
The text was updated successfully, but these errors were encountered: