乐趣区

关于前端:计算机网络-第三章-浏览器的渲染原理及流程

  • 本文转载自 https://mp.weixin.qq.com/s/9G…

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

  • 通常,咱们打包进去的 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 代码是如何被拆分的:

    <body>
        <div>
            <p>hello world</p>
        </div>
    </body>
  • 对于这段代码,能够拆成词:

  • 能够看到,Tag Token 又分 StartTag 和 EndTag,<body>、<div>、<p> 就是 StartTag,</body>、</div>、</p> 就是 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 构造:

    <html>
        <body>
            <div>hello juejin</div>
            <div>hello world</div>
        </body>
    </html>
  • 开始时,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 的继承和层叠两个规定。

    <html>
     <head>
      <link href="./style.css">
      <style>
                .juejin {
                    width: 100px;
                    height: 50px;
                    background: red;
                }
    
                .content {
                    font-size: 25px;
                    line-height: 25px;
                    margin: 10px;
                }
      </style>
     </head>
     <body>
            <div class="juejin">
             <div>CUGGZ</div>
            </div>
            <p style="color: blue" class="content">
                <span>hello world</span>
                <p style="display: none;"> 浏览器 </p>
            </p>
     </body>
    </html>
  • 最终生成的 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 属性来实现脚本的异步加载。

    <script async type="text/javascript" src='./index.js'></script>
    
    <script defer type="text/javascript" src='./index.js'></script>
退出移动版