不能使用正确解析技术,浏览器为html 定制了专属的解析器。

html解析流程

基本示例——符号化下面的html:

Hello world

初始状态为“Data State”,当遇到“<”字符,状态变为“Tag open state”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。

当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。

这样直到遇到“”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“”将和“”一样处理。

树的创建过程

来看一下示例中树的创建过程:

Hello world

构建树这一阶段的输入是符号识别阶段生成的符号序列。

首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。

状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。

现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。

然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。

接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。

html树的构建过程

解析结束时的处理 Action when the parsing is finished在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。

文档状态将被设置为完成,同时触发一个load事件。

浏览器容错 Browsers error tolerance详见 http://www.aiuxian.com/article/p-1832762.html

三 、脚本解析 Parsing scripts

web的模式是同步的,开发者希望解析到一个script 标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完,如果脚本是外因的,则网络必须先请求到这个资源 --- 这个过程也是同步的,会阻塞文档的解析直到资源被请求到。预解析webkit 和firefox 都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源,这种方式可以使 资源并行加载从而使整体速度更快。需要注意的是, 预解析并不改变dom树,它将这个工作留给主解析过程,自己只解析外面资源的引用,比如外部脚本、样式表及图片样式表样式表采用另一种不同的模式。理论上,既然样式表不能改变dom 树,也就没有必要停下文档的解析等待它们,然后存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很度问题,这看起来是个边缘情况,但确实很常见。firefox 在存在样式表还在加载和解析时阻塞所有的脚本,而chrom 只在当脚本视图访问某些可能被未被加载的样式表所影响的特定的样式属性时才阻塞这些脚本。渲染树的构造 Render tree construction当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。Firefox将渲染树中的元素称为frameswebkit则用renderer或渲染对象来描述这些元素。一个渲染对象知道怎么布局及绘制自己及它的children。RenderObject是Webkit的渲染对象基类,它的定义如下:

class RenderObject{

virtual void layout();

virtual void paint(PaintInfo);

virtual void rect repaintRect();

Node* node; //the DOM node

RenderStyle* style; // the computed style

RenderLayer* containgLayer; //the containing z-index layer

}

每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,正如css2所描述的那样,它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响(参考样式计算章节)。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。

webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)

{

Document* doc = node->document();

RenderArena* arena = doc->renderArena();

...

RenderObject* o = 0;

switch (style->display()) {

case NONE:

break;

case INLINE:

o = new (arena) RenderInline(node);

break;

case BLOCK:

o = new (arena) RenderBlock(node);

break;

case INLINE_BLOCK:

o = new (arena) RenderBlock(node);

break;

case LIST_ITEM:

o = new (arena) RenderListItem(node);

break;

...

}

return o;

}

元素的类型也需要考虑,例如,表单控件和表格带有特殊的框架。

染树和Dom树的关系 The render tree relation to the DOM tree渲染对象和dom 元素相对应,但这种对应关系不是一对一的,不可见dom 元素不会被插入渲染树,例如head 元素。另外display 属性为none 的元素也不会在渲染树中出现(visibility 属性为hidden 的元素将出现在渲染树中。)四: 布局当渲染对象被创建并添加到树中,它们比你更没有位置和大小,计算这些值的过程称为layout 和reflowhtml 是基于流的布局模型, 意味着发部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性。所以布局可以在文档中从右到左、自上而下进行。也存在一些例外,比如html table坐标系统相对于根frame ,使用top 和left 坐标布局是一个递归的过程,由根渲染对象开始,它对应html 文档元素,布局继续递归的过程一些或所有的frame 层级,为我每个需要几何信息的渲染对象进行计算。根渲染对象的位置是0,1 ,它的大小是viewport -- 浏览器窗口的可见部分。所有的渲染对象都有一个layout 和reflow 方法,每个渲染对象调用需要布局的children 的layout 方法Dirty bit 系统为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit 系统,一个渲染对象发生了变化或是被添加了,就标记它及他的children 为dirty - 需要layout ,存在两个标识-dirty 及 children are dirty,children are dirty说明即使这个渲染对象可能没问题,但它至少有一个child需要layout。 全局和增量layout 当layout 在整颗渲染树触发时,称为全局layout ,这可能在下面这些情况下发生1. 一个全局的样式改变影响所有的渲染对象,比如字号的改变2. 窗口resize layout 也可以是增量的,这样只有标志为dirty 的渲染对象会重新布局(也将导致一些额外的布局)。增量layout会在渲染对象dirty 时异步触发,例如,当网络接收到新的内容并添加到dom树后,新的渲染对象会添加到渲染树中。增量layout增量layout 的过程是异步的,firefox为整理layout 生成了reflow 队列,以及一个调度执行这些处理命令。webkit 也有一个计时器用来执行增量layout 遍历树,为dirty状态的渲染对象重新布局另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。全局的layout 一般都是同步触发有些时候,layout 会被作为一个初始layout 之后的回调,比如滑动条的滑动优化当一个layout因为resize 或是渲染位置改版(并不是大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会重新计算。一般情况下,如果只有子树发生改变,则layout 并不从跟开始,这种情况发生在,变化发生在元素自身但并不影响其他周围元素,例如,将文本插入文本域(否则每次都将触发从根开始的重排)layout过程layout一般有下面这几个部分:

1. 父渲染对象决定它自己的宽度

2. 父渲染对象读取chilidren,并: