浏览器渲染流程以下图所示:css
图片来源:www.html5rocks.com/en/tutorial…html
大概能够划分红如下几个步骤:html5
解析的过程分为两个步骤:词法分析和语法分析。
词法分析负责将输入内容分解成一个个有效标记;而语法分析负责根据语言的语法规则分析文档的结构,从而构建解析树。经过词法分析能够将无关的字符(好比空格和换行符)分离出来。web
解析是一个迭代的过程。一般,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。若是发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,而后继续请求下一个标记。算法
若是没有规则能够匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与全部内部存储的标记匹配的规则。若是找不到任何匹配规则,解析器就会引起一个异常。这意味着文档无效,包含语法错误。浏览器
不少时候,解析树还不是最终产品。解析一般是在转译过程当中使用的,而转译是指将输入文档转换成另外一种格式。编译就是这样一个例子。编译器可将源代码编译成机器代码,具体过程是首先将源代码解析成解析树,而后将解析树翻译成机器代码文档。缓存
解析器的输出“解析树”是由 DOM 元素和属性节点构成的树结构。DOM 是文档对象模型 (Document Object Model) 的缩写。它是 HTML 文档的对象表示,同时也是外部内容(例如 JavaScript)与 HTML 元素之间的接口。
解析树的根节点是“Document”对象。markdown
DOM 与标记之间几乎是一一对应的关系。好比下面这段标记:
网络
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
复制代码
可翻译成以下的 DOM 树:框架
HTML5 规范详细地描述了解析算法。此算法由两个阶段组成:标记化和树构建。
标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。
标记生成器识别标记,传递给树构造器,而后接受下一个字符以识别下一个标记;如此反复直到输入的结束。
和 HTML 不一样,CSS 是上下文无关的语法。事实上,CSS 规范定义了 CSS 的词法和语法。
WebKit 使用 Flex 和 Bison 解析器生成器,经过 CSS 语法文件自动建立解析器。Bison 会建立自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每一个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其余与 CSS 语法对应的对象。
网络的模型是同步的。网页解析器遇到 <script>
标记时文档的解析将中止,直到脚本执行完毕。若是脚本是外部的,那么解析过程会中止,直到从网络同步抓取资源完成后再继续。你能够在<script>
标签上添加“defer”属性(<script defer>
),这样它就不会中止文档解析,而是等到解析结束才执行。HTML5 增长了一个async属性,可将脚本标记为异步<script async>
),以便由其余线程解析和执行。
WebKit 和 Firefox 都进行了这项优化。在执行脚本时,其余线程会解析文档的其他部分,找出并加载须要经过网络加载的其余资源。经过这种方式,资源能够在并行链接上加载,从而提升整体速度。请注意,预解析器不会修改 DOM 树,而是将这项工做交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。
另外一方面,样式表有着不一样的模型。理论上来讲,应用样式表不会更改 DOM 树,所以彷佛没有必要等待样式表并中止文档解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。若是当时尚未加载和解析样式,脚本就会得到错误的回复,这样显然会产生不少问题。这看上去是一个非典型案例,但事实上很是广泛。Firefox 在样式表加载和解析的过程当中,会禁止全部脚本。而对于 WebKit 而言,仅当脚本尝试访问的样式属性可能受还没有加载的样式表影响时,它才会禁止该脚本。
Render tree是由 DOM 和 CSSOM 组合构建而成的。也是页面可视化元素按照其显示顺序而组成的树,是文档的可视化表示。它的做用是让浏览器按照正确的顺序绘制内容。
Firefox 将Render tree中的元素称为“框架”。WebKit 使用的术语是呈现器或呈现对象。
呈现器知道如何布局并将自身及其子元素绘制出来。
呈现器是和 DOM 元素相对应的,但并不是一一对应。非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。若是元素的 display 属性值为“none”,那么也不会显示在呈现树中(可是 visibility 属性值为“hidden”的元素仍会显示)。
有一些 DOM 元素对应多个可视化对象。它们每每是具备复杂结构的元素,没法用单一的矩形来描述。例如,“select”元素有 3 个呈现器:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。若是因为宽度不够,文本没法在一行中显示而分为多行,那么新的行也会做为新的呈现器而添加。
另外一个关于多呈现器的例子是格式无效的 HTML。根据 CSS 规范,inline 元素只能包含 block 元素或 inline 元素中的一种。若是出现了混合内容,则应建立匿名的 block 呈现器,以包裹 inline 元素。
有一些呈现对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不一样。浮动定位和绝对定位的元素就是这样,它们处于正常的流程以外,放置在树中的其余地方,并映射到真正的框架,而放在原位的是占位框架。
当Render Tree刚构建完时,并不包含元素节点的位置和大小信息。计算这些值的过程称为布局或重排。
HTML 采用基于流的布局模型,这意味着大多数状况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素一般不会影响靠前位置元素的几何特征,所以布局能够按从左至右、从上至下的顺序遍历文档。可是也有例外状况,好比 HTML 表格的计算就须要不止一次的遍历。
坐标系是相对于根框架而创建的,使用的是上坐标和左坐标。
布局是一个递归的过程。它从根呈现器(对应于 HTML 文档的 元素)开始,而后递归遍历部分或全部的框架层次结构,为每个须要计算的呈现器计算几何信息。
根呈现器的位置左边是 0,0,其尺寸为视口(也就是浏览器窗口的可见区域)。
全部的呈现器都有一个“layout”或者“reflow”方法,每个呈现器都会调用其须要进行布局的子代的 layout 方法。
为避免对全部细小更改都进行总体布局,浏览器采用了一种“dirty 位”系统。若是某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则须要进行布局。
有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代须要布局。
全局布局是指触发了整个呈现树范围的布局,触发缘由可能包括:
布局能够采用增量方式,也就是只对 dirty 呈现器进行布局(这样可能存在须要进行额外布局的弊端)。
当呈现器为 dirty 时,会异步触发增量布局。例如,当来自网络的额外内容添加到 DOM 树以后,新的呈现器附加到了呈现树中。
在绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工做是使用用户界面基础组件完成的。
和布局同样,绘制也分为全局(绘制整个呈现树)和增量两种。在增量绘制中,部分呈现器发生了更改,可是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这致使 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。在 Chrome 浏览器中,状况要更复杂一些,由于 Chrome 浏览器的呈现器不在主进程上。Chrome 浏览器会在某种程度上模拟 OS 的行为。展现层会侦听这些事件,并将消息委托给呈现根节点。而后遍历呈现树,直到找到相关的呈现器,该呈现器会从新绘制本身(一般也包括其子代)。
CSS2 规范定义了绘制流程的顺序。绘制的顺序其实就是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,所以这样的顺序会影响绘制。块呈现器的堆栈顺序以下:
在从新绘制以前,WebKit 会将原来的矩形另存为一张位图(Bitmap),而后只绘制新旧矩形之间的差别部分。
在发生变化时,浏览器会尽量作出最小的响应。所以,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大“html”元素的字体)会致使缓存无效,使得整个呈现树都会进行从新布局和绘制。
参考连接