浏览器将HTML,css, javascript代码转换成屏幕上展示的实际像素,这期间会经历几个过程:

  • 构建对象模型(DOM, CSSOM)
  • 构建渲染树(RenderTree)
  • 布局
  • 渲染

第一步DomTree的构建

【201912-D】浏览器工作原理与实践 - 图1

为什么有时在js中访问DOM时浏览器会报错呢?

在dom解析过程,如果碰到了script/link标签,则会根据src对应的地址去加载资源,在script标签没有设置async/defer属性时,这个加载过程是下载并执行完全部的代码,此时,DOM树还没有完全创建完毕,这个时候如果js企图访问script标签后面的DOM元素,浏览器就会抛出找不到该DOM元素的错误。
值得注意的是:从bytes到Tokens的这个过程,浏览器都可以交给其他单独的线程去处理,不会堵塞浏览器的渲染线程。但是后面的部分就都在渲染线程下进行了,也就是我们常说的js单线程环境。

第二部 CSSOMTree的构建

页面除了dom的解析成页面外,需要对页面修饰,还需要对css做解析, CSSOM构建会经历的过程如下:

  • 解析
  • Token化
  • 生成Nodes并构建CSSOMTree

假设浏览器收到如下CSS代码

  1. body {font-size: 16px;}
  2. p {font-weight: bold;}
  3. p span {display:none;}
  4. span {color: red;}
  5. img {float: right;}

【201912-D】浏览器工作原理与实践 - 图2 页面加载的顺序
DOM,CSS解析成对应的DOM都会开启加载(并行),但是CSSOMTree渲染,会等到所有css解析构建完成才可以被使用。

第三部分 构建Render Tree

现在,我们已经拥有了完整的DOM树和CSSOM树。DOM 树上每一个节点对应着网页里每一个元素,CSSOM树上每个节点对应着网页里每个元素的样式,并且此时浏览器也可以通过 JavaScript 操作DOM/CSSOM树,动态改变它的结构。但是DOM/CSSOM树本身并不能直接用于排版和渲染,浏览器还会生成另外一棵树:Render树

第四部分 RenderObject and RenderLayer

浏览器渲染引擎并不是直接使用Render树进行绘制,为了方便处理Positioning,Clipping,Overflow-scroll,CSS Transfrom/Opacrity/Animation/Filter,Mask or Reflection,Z-indexing等属性,浏览器需要生成另外一棵树:Layer树

浏览器会为一些特定RenderObject生成对应的RenderLayer,其中的规则是:

  • 是否是页面的根节点 It’s the root object for the page
  • 是否有css的一些布局属性(relative absolute or a transform) It has explicit CSS position properties (relative, absolute or a transform)
  • 是否透明 It is transparent
  • 是否有溢出 Has overflow, an alpha mask or reflection
  • 是否有css滤镜 Has a CSS filter
  • 是否包含一个canvas元素使得节点拥有视图上下文 Corresponds to canvas element that has a 3D (WebGL) context or an accelerated 2D context
  • 是否包含一个video元素 Corresponds to a video element

当满足上面其中一个条件时,这个RrenderObject就会被浏览器选中生成对应的RenderLayer。至于那些没有被命运选中的RrenderObject,会从属与父节点的RenderLayer。最终,每个RrenderObject都会直接或者间接的属于一个RenderLayer。
浏览器渲染引擎在布局和渲染时会遍历整个Layer树,访问每一个RenderLayer,再遍历从属于这个RenderLayer的 RrenderObject,将每一个 RenderObject 绘制出来。可以理解为:Layer 树决定了网页绘制的层次顺序,而从属于RenderLayer 的 RrenderObject决定了这个 Layer 的内容,所有的 RenderLayerRrenderObject 一起就决定了网页在屏幕上最终呈现出来的内容。

第五部分 布局

到目前为止,浏览器计算出了哪些节点是可见的以及它的信息和样式,接下来就需要计算这些节点在设备视口内的确切位置和大小,这个过程我们称之为“布局”。
布局最后的输出是一个“盒模型”:将所有相对测量值都转换成屏幕上的绝对像素。

第六部分 渲染

最后,既然我们知道了哪些节点可见、它们的计算样式以及几何信息,我们终于可以将这些信息传递给最后一个阶段:将渲染树中的每个节点转换成屏幕上的实际像素:浏览器通过发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素

至此,我们就能够在浏览器上看到漂亮的网页了

谈及页面性能优化,我们也常说要尽量减少浏览器的重排和重绘,浏览器重排和重绘时究竟做了哪些工作呢?

我们平时常说的重排,其实就是浏览器计算render树,布局到渲染的这个过程,而重绘就是计算layer树到渲染的这个过程,每当触发一次重绘和重排时,浏览器都需要重新经过一遍上述的计算。很显然,重排会产生比重绘更大的开销,但无论是重排还是重绘,都会给浏览器渲染线程造成很大的负担,所以,我们在实际生产中要严格注意减少重排和重绘的触发。至于如何减少重排和重绘的次数,这里就不多做展开了,详细请听下回分解~

开启非安全模式浏览器
open -n /Applications/Google\ Chrome.app/ —args —disable-web-security —user-data-dir=/Users/wangyangyang/Documents/chromeDevUserData

参考

极客浏览器工作原理
http://blog.poetries.top/browser-working-principle/guide/part1/lesson01.html#%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B
浏览器的渲染过程
https://zhuanlan.zhihu.com/p/74792085
深入浅出浏览器原理
https://blog.fundebug.com/2019/01/03/understand-browser-rendering/
你不知道的浏览器页面渲染机制
https://juejin.im/post/5ca0c0abe51d4553a942c17d