计算机网络 第三章 浏览器的渲染原理及流程

一、浏览器的渲染原理及流程

  • 通常,我们打包出来的 HTML、CSS、JavaScript 等文件,经过浏览器运行之后就会显示出页面,这个过程就是浏览器的渲染进程来操作实现的。渲染进程的主要任务就是将静态资源转化为可视化界面

1、渲染流程

  • DOM树构建:渲染引擎使用HTML解析器(调用XML解析器)解析HTML文档,将各个HTML元素逐个转化成DOM节点,从而生成DOM树;
  • CSSOM树构建:CSS解析器解析CSS,并将其转化为CSS对象,将这些CSS对象组装起来,构建CSSOM树;
  • 渲染树构建:DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两棵树构建出一棵渲染树;
  • 页面布局:渲染树构建完毕之后,元素的位置关系以及需要应用的样式就确定了,这时浏览器会计算出所有元素的大小和绝对位置;从而有了盒模型。
  • 页面绘制:页面布局完成之后,浏览器会将根据处理出来的结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。

2、DOM树的构建

  • 为什么要构建DOM树呢? 这是因为,浏览器无法直接理解和使用 HTML,所以需要将HTML转化为浏览器能够理解的结构——DOM树。
  • DOM 树描述的是 HTML 标签的层级关系。在页面中,每个HTML标签都会被浏览器解析成文档对象。HTML本质上就是一个嵌套结构,在解析时会把每个文档对象用一个树形结构组织起来,所有的文档对象都会挂在document上,这种组织方式就是HTML最基础的结构——文档对象模型(DOM),这棵树的每个文档对象就叫做DOM节点。
  • 在渲染引擎内部,HTML 解析器负责将 HTML 字节流转换为 DOM 结构,其转化过程如下:

1)字符流 → 词(token)

  • HTML结构首先会通过状态机做词法分析的,将字符流分解为词(token)。Token分为Tag Token 和文本 Token。
  • 下面来看一个HTML代码是如何被拆分的:

    
        

    hello world

  • 对于这段代码,可以拆成词:

  • 可以看到,Tag Token 又分 StartTag 和 EndTag,、

    就是 StartTag ,、

    就是 EndTag,分别对应图中的蓝色和红色块,文本 Token 对应绿色块。

2)词(token)→ DOM树

  • 接下来就需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。
  • 这个过程是通过栈结构来实现的,这个栈主要用来计算节点之间的父子关系,上面步骤中生成的token会按顺序压入栈中,该过程的规则如下:

    • 如果分词器解析出来是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,并执行入栈操作,它的父节点就是栈中相邻的那个元素生成的节点;
    • 如果分词器解析出来是 文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点;
    • 如果分词器解析出来的是EndTag Token,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div从栈中弹出,表示该 div 元素解析完成。
  • 下面来看看这的Token栈是如何工作的,有如下HTML结构:

    
        
            
    hello juejin
    hello world
  • 开始时,HTML解析器会创建一个根为 document 的空的 DOM 结构,同时将 StartTag document 的Token压入栈中,然后再将解析出来的第一个 StartTag html 压入栈中,并创建一个 html 的DOM节点,添加到 document 上,接下来body和div标签也会和上面的过程一样,进行入栈操作:

  • 随后就会解析到 div标签中的文本Token,渲染引擎会为该 Token 创建一个文本节点,并将该 Token 添加到 DOM 中,它的父节点就是当前 Token 栈顶元素对应的节点:

  • 接下来就是第一个EndTag div,这时 HTML 解析器会判断当前栈顶元素是否是 StartTag div,如果是,则从栈顶弹出 StartTag div,如下图所示:

  • 再之后的过程就和上面类似了,最终的结果如下:

3、CSSOM树的构建

  • 实际上。浏览器在构建 DOM 树的同时,如果样式也加载完成了,那么 CSSOM 树也会同步构建。
  • CSSOM 树和 DOM 树类似,它主要有两个作用:

    • 提供给 JavaScript 操作样式的能力;
    • 为渲染树的合成提供基础的样式信息。
  • CSSOM 树描述的是选择器之间的层级关系。

1)属性值标准化

  • 在将CSS转化为树形对象之前,还需要将样式表中的属性值进行标准化处理,比如,当遇到以下CSS样式:

    body { font-size: 2em }
    p {color:blue;}
    div {font-weight: bold}
    div p {color:green;}
    div {color:red; }
  • 可以看到上面CSS中有很多属性值,比如2em、blue、red、bold等,这些数值并不能被浏览器直接理解。所以,需要将所有值转化为浏览器渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

    body { font-size: 32px }
    p {color: rgb(0, 0, 255);}
    div {font-weight: 700}
    div p {color: (0, 128, 0);}
    div {color: (255, 0, 0); }

2)样式计算

  • 样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。

    
     
      
      
     
     
            
    CUGGZ

    hello world

    浏览器

  • 最终生成的CSSOM树大致如下:

4、渲染树的构建

  • 为什么要构建渲染树呢? DOM树可能包含一些不可见的元素,比如head标签,使用display:none;属性的元素等。所以在显示页面之前,还要额外地构建一棵只包含可见元素的渲染树
  • 渲染树就是 DOM 树和 CSSOM 树的结合,会得到一个可以知道每个节点会应用什么样式的数据结构。这个结合的过程就是遍历整个 DOM 树,然后在 CSSOM 树里查询到匹配的样式。
  • 在不同浏览器里,构建渲染树的过程不太一样:

    • 在 Chrome 里会在每个节点上使用 attach() 方法,把 CSSOM 树的节点挂在 DOM 树上作为渲染树。
    • 在 Firefox 里会单独构造一个新的结构, 用来连接 DOM 树和 CSSOM 树的映射关系。
  • 同一个 DOM 节点可能会匹配到多个 CSSOM 节点,而最终的效果由哪个 CSS 规则来确定,就是样式优先级的问题了。当一个 DOM 元素受到多条样式控制时,样式的优先级顺序如下:内联样式 > ID选择器 > 类选择器 > 标签选择器 > 通用选择器 > 继承样式 > 浏览器默认样式

5、页面布局

  • 通过计算渲染树上每个节点的样式,就能得出来每个元素所占空间的大小和位置。当有了所有元素的大小和位置后,就可以在浏览器的页面区域里去绘制元素的边框了。这个过程就是布局。
  • 这个过程中,浏览器对渲染树进行遍历,将元素间嵌套关系以盒模型的形式写入文档流。

6、页面绘制

1)构建图层

  • 经过布局,每个元素的位置和大小就有了,那下面是不是就该开始绘制页面了?答案是否定的,因为页面上可能有很多复杂的场景,比如3D变化、页面滚动、使用z-index进行z轴的排序等。所以,为了实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树。
  • 图层会按照一定顺序叠加在一起,就形成了最终的页面。这里,将页面分解成多个图层的操作就成为分层, 最后将这些图层合并到一层的操作就成为合成, 分层和合成通常是一起使用的。Chrome 引入了分层和合成的机制就是为了提升每帧的渲染效率。
  • 通常情况下,并不是渲染树上的每个节点都包含一个图层,如果一个节点没有对应的图层,那这个节点就会属于其父节点的图层。
(1)拥有层叠上下文属性的元素
  • 我们看到的页面通常是二维的平面,而层叠上下文能够让页面具有三维的概念。这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。
  • 包括浮动、定位、z-index。
(2)需要裁剪的元素
  • 假如有一个固定宽高的div盒子,而里面的文字较多超过了盒子的高度,这时就会产生裁剪,浏览器渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。
  • 当出现裁剪时,浏览器的渲染引擎就会为文字部分单独创建一个图层,如果出现滚动条,那么滚动条也会被提升为单独的图层。

2)绘制图层

  • 在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,下面就来看看渲染引擎是怎么实现图层绘制的。
  • 渲染引擎在绘制图层时,会把一个图层的绘制分成很多绘制指令,然后把这些指令按照顺序组成一个待绘制的列表:

  • 通常情况下,绘制一个元素需要执行多条绘制指令,因为每个元素的背景、边框等属性都需要单独的指令进行绘制。
  • 绘制列表只是用来记录绘制顺序和绘制指令的列表,而绘制操作是由渲染引擎中的合成线程来完成的。当图层绘制列表准备好之后,主线程会把该绘制列表提交给合成线程。所以,在执行合成操作时并不会影响到主线程的执行。

二、扩展

1、重排和重绘

1)重排

  • 当我们的操作引发了 DOM 树中几何尺寸的变化(改变元素的大小、位置、布局方式等),这时渲染树里有改动的节点和它影响的节点都要重新计算。这个过程就叫做重排,也称为回流。
  • 在改动发生时,要重新经历页面渲染的整个流程,所以开销是很大的。

2)重绘

  • 当对 DOM 的修改导致了样式的变化(比如修改颜色、背景色)、但未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(会跳过重排环节),这个过程叫做重绘。
  • 当我们修改元素绘制属性时,页面布局阶段不会执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

2、JS 对 DOM 的影响

  • 当解析器解析HTML时,如果遇到了script标签,判断这是脚本,就会暂停 DOM 的解析,因为接下来的 JavaScript 脚本可能会修改当前已经生成的 DOM 结构。
  • JavaScript 线程会阻塞 DOM 的解析,我们可以通过CDN、压缩脚本等方式来加速 JavaScript 脚本的加载。
  • 如果脚本文件中没有操作DOM的相关代码,就可以将JavaScript脚本设置为异步加载,可以给script标签添加 async 或 defer 属性来实现脚本的异步加载。

    
    
    

你可能感兴趣的:(前端)