Insidelookatmodernwebbrowser是介绍浏览器实现原理的系列文章。共有4篇文章。本次精读介绍第三篇文章。在此宏介绍中概述了渲染器进程的作用。浏览器选项卡中的html、css、javascript内容基本都是由renderer进程的主线程处理的,只是有些js代码会放在webworker或者serviceworker中,所以主线程的核心工作浏览器是解析网页三剑客,生成可用的交互用户界面。在解析阶段,首先renderer进程的主线程会将HTML文本解析成DOM(DocumentObjectModel),只翻译成中文为DocumentObjectModel,所以必须先对文本进行结构化处理才能继续.不仅是浏览器,代码的解析也要先经过Parse阶段。对于需要加载远程资源的htmllink、img、script标签,浏览器会调用网络线程优先并行处理,但是遇到script标签必须先停下来执行,因为js代码可能会改变任何dom对象,这可能导致浏览器不得不重新解析。所以如果你的代码没有修改dom的副作用,可以添加async、defer标签,或者js模块,这样浏览器就不用等待js的执行了。仅DOM不足以进行样式计算。style标签声明的样式需要应用到DOM上。因此,浏览器需要基于DOM生成CSSOM。这个CSSOM主要是根据css选择器(selector)来确定动作节点。布局有了DOM,CSSOM仍然不足以绘制网页,因为我们只知道结构和样式,不知道元素的位置,这就需要生成LayoutTree来描述布局的结构。LayoutTree和DOM的结构非常相似,但是display:none等元素不会出现在LayoutTree上,所以LayoutTree只考虑渲染结构,而DOM是综合描述结构,不适合直接渲染。原文中特别提到LayoutTree有一个很大的技术难点,就是排版。Chrome有一整个团队致力于克服这一技术难题。为什么排版这么难?你可以从这几个例子中体会到冰山一角:盒子模型之间的碰撞,字体拉伸内容导致换行,触发更大区域的重新排列,一个盒子模型拉伸挤压另一个盒子模型,但另一个盒子的大小modelchanges后来内容布局也发生了变化,导致boxmodel再次发生变化,进而导致其他外部boxmodel布局发生变化。布局最难的部分是需要尽可能合理地处理所有奇怪的布局公式,而且很多时候布局公式的规则相互冲突。而且这甚至没有考虑到布局引擎的更改将导致数亿网页出现未知错误的风险。用DOM、CSSOM、LayoutTree绘图就够了吗?还没有,缺少最后一个链接PaintRecord,它指的是绘图记录,它记录了元素的层级关系,以确定元素绘制的顺序。因为LayoutTree只决定物理结构,不决定元素的上下空间结构。有了DOM、CSSOM、LayoutTree和PaintRecord,你终于可以画画了。但是,当HTML发生变化时,重绘的代价是巨大的,因为以上任何一步的计算结果都依赖于上一步。当HTML发生变化时,DOM、CSSOM、LayoutTree、PaintRecord都需要重新计算。大多数时候,浏览器可以在16ms内完成,保持FPS在60左右,但是当页面结构过于复杂时,这些计算本身就超过了16ms,或者js代码被阻塞,会导致用户卡顿.当然对于js的卡顿问题,可以在空闲的时候使用requestAnimationFrame将逻辑操作分散在各个frame中,也可以独立在webworker中。合成绘图的步骤称为光栅化。Chrome刚发布的时候,采用了一种比较简单的光栅化方案,即只渲染可用区域的像素,滚动后补充当前滚动位置的像素进行渲染。这样做会导致渲染永远滞后于滚动。现在普遍采用一种比较成熟的合成技术(compositing),即渲染内容分层绘制渲染,可以大大提高性能,并且可以通过CSS属性will-change(不要滥用它)。浏览器会解析LayoutTree,得到LayerTree(图层树),并根据它逐层渲染。Compositinglayers会将绘图内容拆分成多个光栅,在GPU上渲染,所以性能会非常好。精读从渲染分层看性能优化这篇文章提到了浏览器渲染的五个重要环节:解析、样式、布局、绘制和合成。发生在该部分。其实从性能优化的角度来说,解析链接可以换成JS链接,因为现代JS框架往往没有HTML模板内容需要解析,而几乎所有的JS都是操作DOM的,所以可以看成5新链接:JS、样式、布局、绘图、合成。值得注意的是,几乎每一层的计算都依赖于上层的结果,但并不是每一层都一定会重复计算。我们需要特别注意以下几种情况:修改元素的几何属性(位置、宽度和高度等)会触发所有图层,因为这是一个非常重量级的修改。修改元素的绘图属性(如颜色、背景色)不影响位置,会跳过布局层。修改transform等属性以跳过布局和绘图层似乎很奇怪。对于第三点,由于transform的内容会被提升到合成层由GPU渲染,不会和浏览器主线程的布局绘制一起处理,所以视觉上这个元素确实有一个位移,但是它与修改left和top的位移在实现上有本质区别。因此,站在浏览器开发者的角度,很容易理解为什么这种优化不是trick,因为浏览器本身的实现将layout、drawing和composition层的行为分开了,不同的底层方案代码不同,表现肯定会有所不同。您可以使用csstriggers查看哪些层将由不同的css属性重新计算。当然,作为开发者,你仍然可以抱怨为什么浏览器不能“自动屏蔽左上角和变换的实现细节,自动进行合理的分层”。但是,如果浏览器厂商做不到这一点,开发者还是会主动去了解Realize原理。Implicitcompositionlayer,layerexplosion,layerautomaticmerging除了transform和will-change属性,还有很多情况下元素会被提升到合成层,比如video,canvas,iframe,或者fixed元素,但是这些有规则明确,所以属于展示合成。隐式合成是指元素没有被特别标记,但也被提升到合成层的情况。这种情况经常发生在z-index元素重叠时,下层元素的显示声明被提升到复合层。为了确保z-index覆盖关系,上面的元素被隐式提升到合成层。层次爆炸指的是隐式组合的原因。当css中出现一些复杂的行为时(比如轨迹动画),浏览器无法实时捕捉到哪些元素在当前元素之上,因此不得不将所有元素提升到合成层。当合成层数过多时,主线程与GPU的通信可能会成为瓶颈,反而会影响性能。浏览器还支持层的自动合并。例如,当隐式提升为复合层时,多个元素将自动合并到一个复合层中。但这种方法并不总是可靠的。毕竟自动处理无法猜到开发者的意图,所以最好的优化方式就是开发者的主动介入。我们只需要注意把所有提升到复合层的元素放在z-index上面,让浏览器有判断的依据,我们就不用担心这个元素会不会突然动了到某个元素的位置,导致它被抑制了那个元素,所以这个元素必须隐式提升到复合层,以保证它们之间的正确顺序,因为这个元素本来是位于其他元素之上的。小结看完本文,希望大家根据浏览器在渲染过程中的实现原理,总结出更多代码层面的性能优化经验。最后要吐槽的是,由于浏览器规范一步步迭代,看似描述位置的css属性,实际实现方式不一样。虽然这个规则在W3C规范中有所体现,但是如果只是属性名是很难看出端倪的,所以想要做极致的性能优化,就必须了解浏览器实现的原理。讨论地址为:Jingdu《深入了解现代浏览器三》·Issue#379·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号
