-- W e l c o m e t o N o k e y _ b l o g --

那些年,被无数前端撸过的“性能”(又名:你要是不知道重排和重绘都不好意思去面试)

4月 10 2015

在这个年代写前端的我们是幸福的。

大学时期看过一本书--《黑客》。很极致的表现了黑客的文化,真是看的我热血沸腾,好吧,我承认我是在打广告,如果不介意,顺便插入一段简介吧:“从20世纪50年代早期跨越到80年代后期,追述了计算机革命中初期黑客的丰功伟绩,他们都是最聪明和最富有个性的精英。他们勇于承担风险,勇于挑战规则,并把世界推向了一个全新的发展方向。这本书涵盖了一些著名黑客的最新资料,包括比尔•盖茨、马克•扎克伯格、理查德•托斯曼和史蒂夫•沃兹尼亚克,并讲述了从早期计算机研究实验室到最初的家用计算机期间一些妙趣横生的故事。”

OK,视线回到21世纪来吧,毕竟活在当下才是正题,而在“当下”写代码更是重中之重,作为一名前端开发,我就来讲一讲“古猿”们非常重视的性能问题,这也是我们幸福的原因之一。虽然我刚踏入前端圈不久,也是深有体会:手机硬件性能的不断提升,H5技术大行其道(这个不是贬义词吧?),外加前不久IE6退役的消息,还有“React Native”、“Node.js”,这两个是我比较喜欢的,哈哈。其实性能被渐渐忽略也是有原因的,也是符合趋势的,但是矛盾并不会消失,它只是不知不觉转移到了别的地方。计算机技术也没有那么神秘了,而是真正慢慢进入人们的生活,成为改善人们生活的推动力了。

写在前面:我接下来写的东西完全是自己的理解,既不代表正义的一方,也不代表邪恶的一方,还得需要你们自己去分析形势,选择立场啊!(Webkit内核)

记得毕业那段时间,去百度面试的经历(二面挂了 T_T),面试官就问了我知不知道重排(回流)重绘,然后我就很天真的看着他,说:“不知道”(呵呵,没戏了)。其实说到性能,就离不开浏览器,而重排和重绘也是浏览器的重要的职能之一,好,那就先简单说一下浏览器加载页面的过程吧,先看一张图:

Image

这张图实际是浏览器的加载过程:蓝色的线代表的是DOMContentLoaded Event触发的时刻;红色的线代表的是Load Event触发的时刻,我们可以在结合一张图看(Timeline):

Image

通过这张图我们可以更清楚的看到浏览器的一些请求行为,包括第一次请求index首页,到后续的一些css文件js文件图片文件的请求。这一部分有一些我理解的点,我们可以看到第二张图片多了一条绿色的线,这个是浏览器第一次在屏幕渲染页面的时间点(也就是第一次显示出东西来的时间点),我通过观察Timeline分析并猜测,First Paint总是发生在全部css文件加载完成之后,而DOMContentLoaded则是发生在css和js文件全部加载完以后。在H5的一些宣传页上,我总是希望用户能够很快看到Loading页面,尤其是在网速慢的情况下,如果延迟加载js,那么loading页面就不能用进度条这种形式,但是无限circle的加载又会很容易让人烦躁,最终权衡下来,我还是推荐用进度条的加载形式。(PS:关于图片的请求,如果是以img标签的形式写在HTML里面,请求会发生在DOMContentLoaded之前,如果写在css文件里面,请求发生在DOMContentLoaded之后。如果图片数量很多的话,建议写在css的background-image里面,可参见淘宝首页,哈哈。)

貌似有点跑题了。。。看完了浏览器的加载过程,接下来看看DOM的渲染吧:

Image

我们都知道DOM树渲染树(接下来都是我自己的理解了):当index.html加载完成后,会触发一个Parse HTML行为,它在生成DOM树的同时会发出页面中的一些异步请求,按理来说这些都是异步请求,理论上是同时请求的,但是我观察了下Network,浏览器貌似会对并发资源请求的数量进行一定的限制,Search一下后,发现果然浏览器对同一域名下的并发资源请求链接数有一定的限制,如果请求的资源较多,尽量将资源分布在不同的服务器上,例如淘宝,你会发现图片会分属在不同的域名下:gtms01.alicdn.com、gtms02.alicdn.com、gtms03.alicdn.com、gtms04.alicdn.com…。

咳咳,请求这块说多了,那么在所有css加载完以后,浏览器会Recalculate Style重新计算样式,然后进行Layout(重排)(第一次布局)Update Layer Tree更新渲染层树,为什么这里有一个的概念呢,看上面那张图你就明白了。接着就进行Paint(重绘)(第一次绘制),重绘的过程其实是为每个元素填充像素点,也就是直接显示在屏幕上的数据,那最后一步的Composite Layers是干什么呢?如果你用过PS,那么这个过程就很好理解了,就是合并图层,如果一个父元素是半透明的红色,子元素是半透明的蓝色,那么合在一起不就是XXX颜色了吗,哈哈!最后把合并后的当前窗口大小的数据流输出到显示内存里,就显示出来啦!(补充:浏览器在滚动的时候不会进行重排和重绘,但Resize基本上会涉及所有的过程。)

重排的代价是比较大的,关于重排的重绘方面的优化问题,可以搜一下张鑫旭前辈翻译的两篇文章:

在优化里面有一条就是尽量用absolute和fixed元素做动画,这就是与重排的另一个关键点了:position

  • static
  • relative
  • absolute
  • fixed

关于这些属性的不同点,可以看我的JSfiddle在线示例。最后在附上我参考的几篇文章,还有css属性的重排和重绘查询表(有的需要翻墙):