游览器是解析渲染机制

渲染引擎:

渲染引擎可以显示html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类数据,例如使用PDF阅读器插件,可以显示PDF格式;Webkit 是一种开放源代码渲染引擎,起初用于 Linux 平台,随后由 Apple 公司进行修改,从而支持苹果机和 Windows。

主流程:

渲染引擎一开始会从网络层获取请求文档的内容,通常以8K分块的方式完成。
获取了文档内容之后,渲染引擎开始正式工作,其基本流程:

  1. 渲染引擎解析HTML文档,并将文档中的标签转化为dom节点树,即”内容树”。同时,它也会解析外部CSS文件以及style标签中的样式数据。
  2. 这些样式信息连同HTML中的”可见内容”一道,被用于构建另一棵树——”渲染树(Render树)”。
  3. 渲染树由一些带有视觉属性(如颜色、大小等)的矩形组成,这些矩形将按照正确的顺序显示在频幕上。
  4. 渲染树构建完毕之后,将会进入”布局”处理阶段,即为每一个节点分配一个屏幕坐标。再下一步就是绘制(painting),即遍历render树,并使用UI后端层绘制每个节点。

注意1:这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
注意2:遇到js/css/image等静态资源时,就向服务器端去请求下载(会使用多线程下载,每个浏览器的线程数不一样),这是时候就用上 keep-alive特性了,建立一次HTTP连接,可以请求多个资源,下载资源的顺序就是按照代码里面的顺序,但是由于每个资源大小不一样,而浏览器又是多线程请求请求资源,所以这里显示的顺序并不一定是代码里面的顺序。
注意3:JS的解析是由浏览器中的JS解析引擎完成的。JS是单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载。

解析与DOM树构建:

1.Html解析:将 HTML 标记解析成解析树。
Html有一个正式的格式定义:DTD(Document Type Definition,文档类型定义),但它并不是上下文无关的语法。这意味着所有的常规解析器都不适用于 HTML,原因在于:语言本身的宽容特性; 浏览器对一些常见的非法html有容错机制; 解析过程需要不断地反复。
解析器的输出(即”解析树”)是由DOM元素及属性节点组成的。DOM是文档对象模型(Document Object Model) 的缩写。它是HTML文档的对象表示,同时也是外部内容(例如 JavaScript)与HTML元素之间的接口。
解析树的根节点是”Document”对象。DOM与标记之间几乎是一一对应的关系。

HTML5规范详细地描述了解析算法。此算法由两个阶段组成:符号化及构建树。
1> 符号化:(符号识别算法)是词法分析的过程,将输入内容解析成多个标记,HTML标记包括起始标记、结束标记、属性名称和属性值。 初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。 接收一个 a-z字符会创建“起始标记”,状态更改为“标记名称状态”。 遇到 > 标记时,会发送当前的标记,状态改回“数据状态”。 接收下一个输入字符 / 时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。
2> 树构建算法:标记生成器识别标记,传递给树构造器, 在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”。
树构建阶段的输入是一个来自标记化阶段的标记序列。第一个模式是“initial mode”, 接收 HTML 标记后转为“before html”模式,并在这个模式下重新处理此标记。这样会创建一个 HTMLHtmlElement 元素,并将其附加到 Document 根对象上。然后状态将改为“before head”, 此时我们接收“body”标记。即使我们的示例中没有“head”标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中。现在我们进入了“in head”模式,然后转入“after head”模式。系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement,同时模式转变为“body”。 现在,接收由“Hello world”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。接收 body 结束标记会触发“after body”模式。现在我们将接收 HTML 结束标记,然后进入“after after body”模式。接收到文件结束标记后,解析过程就此结束。在此阶段,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。

  1. CSS解析:语法是采用 BNF 格式描述的。Webkit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器;Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。

    处理脚本和样式表的顺序:
    1> 脚本
    网络的模型是同步的。网页作者希望解析器遇到<script>标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。
    2> 预解析
    WebKit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度.
    3> 样式表
    另一方面,样式表有着不同的模型。理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。

  2. 渲染树(render树)构建:
    在 DOM 树构建的同时,浏览器还会构建另一个树结构:渲染树。Render树节点和DOM树节点不是一一对应关系, 是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是让您按照正确的顺序绘制内容。Webkit将渲染树中的元素称为呈现器。每一个呈现器都代表了一个矩形的区域,通常对应于相关节点的 CSS 框,框的类型会受到与节点相关的“display”样式属性的影响; 非可视化的 DOM 元素不会插入渲染树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在渲染树中(但是 visibility 属性值为“hidden”的元素仍会显示)。
    1> 渲染树构建流程:
    在 Webkit 中,解析样式和创建呈现器的过程称为“附加”。每个 DOM 节点都有一个“attach”方法。附加是同步进行的,将节点插入 DOM 树需要调用新的节点“attach”方法。
      处理 html 和 body 标记就会构建渲染树根节点。这个根节点呈现对象对应于 CSS 规范中所说的容器 block,这是最上层的 block,包含了其他所有 block。它的尺寸就是视口,即浏览器窗口显示区域的尺寸。Firefox 称之为 ViewPortFrame,而 Webkit 称之为 RenderView。这就是文档所指向的呈现对象。渲染树的其余部分以 DOM 树节点插入的形式来构建。
    2> 样式计算:
    构建渲染树时,需要计算每一个呈现对象的可视化属性;
    (1) 共享样式数据:Webkit 节点会引用样式对象 (RenderStyle)。这些对象在某些情况下可以由不同节点共享。这些节点必须是是同级关系,没有ID等等。
    (2) Firefox 规则树
    为了简化样式计算,Firefox 还采用了另外两种树:规则树和样式上下文树。Webkit 也有样式对象,但它们不是保存在类似样式上下文树这样的树结构中,只是由 DOM 节点指向此类对象的相关样式。样式上下文包含端值。要计算出这些值,应按照正确顺序应用所有的匹配规则,并将其从逻辑值转化为具体的值。所有匹配的规则都存储在树中。路径中的底层节点拥有较高的优先级。规则树包含了所有已知规则匹配的路径。规则的存储是延迟进行的。规则树不会在开始的时候就为所有的节点进行计算,而是只有当某个节点样式需要进行计算时,才会向规则树添加计算的路径。
    (3) 对规则进行处理以简化匹配:
    样式表解析完毕后,系统会根据选择器将 CSS 规则添加到某个哈希表中。这些哈希表的选择器各不相同,包括 ID、类名称、标记名称等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是 ID,规则就会添加到 ID 表中;如果选择器是类,规则就会添加到类表中,依此类推。 这种处理可以大大简化规则匹配。

  3. 布局:
    呈现器在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。HTML采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。布局是一个递归的过程。它从根呈现器(对应于HTML文档的元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。所有的呈现器都有一个“laybout”或者“reflow”方法,每一个呈现器都会调用其需要进行布局的子代的 layout 方法。
    1> Dirty 位系统
    为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则需要进行布局。有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局。
    2> 全局布局和增量布局
    全局布局是指触发了整个呈现树范围的布局,触发原因可能包括:影响所有呈现器的全局样式更改,例如字体大小更改。屏幕大小调整。
    布局可以采用增量方式,也就是只对 dirty 呈现器进行布局(这样可能存在需要进行额外布局的弊端)。当呈现器为 dirty 时,会异步触发增量布局。
    3> 异步布局和同步布局
    增量布局是异步执行的。Firefox 将增量布局的“reflow 命令”加入队列,而调度程序会触发这些命令的批量执行。WebKit 也有用于执行增量布局的计时器:对呈现树进行遍历,并对 dirty 呈现器进行布局。

  4. 绘制:
    在绘制阶段,系统会遍历渲染树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。
    1> 全局绘制和增量绘制
    和布局一样,绘制也分为全局(绘制整个呈现树)和增量两种。在增量绘制中,部分呈现器发生了更改,但是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。在 Chrome 浏览器中,情况要更复杂一些,因为 Chrome 浏览器的呈现器不在主进程上。Chrome 浏览器会在某种程度上模拟 OS 的行为。展示层会侦听这些事件,并将消息委托给呈现根节点。然后遍历呈现树,直到找到相关的呈现器,该呈现器会重新绘制自己(通常也包括其子代).
    2> WebKit 矩形存储
    在重新绘制之前,WebKit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。
    3> Firefox 显示列表
    Firefox 遍历整个呈现树,为绘制的矩形建立一个显示列表。列表中按照正确的绘制顺序(先是呈现器的背景,然后是边框等等)包含了与矩形相关的呈现器。这样等到重新绘制的时候,只需遍历一次呈现树,而不用多次遍历(绘制所有背景,然后绘制所有图片,再绘制所有边框等等)。
    Firefox 对此过程进行了优化,也就是不添加隐藏的元素,例如被不透明元素完全遮挡住的元素。

转载自:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Dirty_bit_system
https://blog.csdn.net/wh_xmy/article/details/79567070

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值