Webkit底层原理(3)--HTML解释器

1. 解释过程

HTML解释器的工作就是将从网络或本地磁盘获取的HTML网页和资源从字节流解释成DOM树结构。如下图:
Webkit底层原理(3)--HTML解释器_第1张图片
图中将这一过程描述得很清晰:首先是字节流,经过解码之后是字符流,然后通过词法分析器会被解释成词语(Tokens),之后经过语法分析器构建成节点,最后这些节点被组成一颗DOM树。

2. 词法分析

在进行词法分析之前,解释器首先要做的就是检查该网页内容使用的编码格式,以便后面使用合适的解码器。如果解释器在HTML网页中找到了设置的编码格式,Webkit会使用相应的解码器将字节流转换成特定格式的字符串。如果没有特殊的格式,词法分析器HTMLTokenizer可以直接进行词法分析。
词法分析的工作都是由HTMLTokenier来完成,简单来说,它就是一个状态机–输入的是字符串,输出的是一个个的词语。因为字节流可能是分段的,所以输入的字符串可能也是分段的,但是这对词法分析器来说没什么特别之处,它会自己维护内部的状态信息。
词法分析器的主要接口是nextToken函数,调用者只需要将字符串传入,然后就会得到一个词语,并对传入的字符串设置相应的信息,表示当前处理完的位置,如此循环。如果词法分析器遇到错误,则报告状态错误码。
而对于词语的类别,Webkit是定义了很少,HTMLToken定义了6种词语类别,包括:DOCTYPE、StartTag、EndTag、Comment、Character和EndOfFile。这里不涉及HTML的标签类型等信息,那是后面语法分析的工作。
Webkit底层原理(3)--HTML解释器_第2张图片

3. XSSAuditor验证词语

XSSAuditor是一个重要的类,是面试中会经常遇到的一个问题。
当词语生成之后,Webkit需要使用XSSAuditor来验证词语流(Token Stream)。XSS指的是Cross Site Security,主要是针对安全方面的考虑。 根据XSS的安全机制,对于解析出来的这些词语,可能会阻碍某些内容的进一步执行,所以XSSAuditor主要负责过滤这些被阻止的内容,只有通过的词语词啊会作后面的处理。详细的内容后面的文章我会再做介绍。

4. 词语到节点,从节点到DOM树

经过词法分析器解释之后的词语随之被XSSAuditor过滤并且在没有被阻止之后,将被Webkit用来构建DOM节点。主要是利用之前分成的6种词语,生成对应的节点。
因为HTML文档的Tag标签是有开始和结束标记的,所以构建这一过程可以使用栈结构来帮忙。其中,使用一个栈来保存元素节点,其中的元素节点是当前有开始标记但是还没有结束标记的元素节点。想象一下HTML文档的特点,例如:

<body>
  <div>
  	<span>span>
  div>
body>

当解释到span标签元素的开始标记时,栈中的元素就是body、div和span,当遇到span的结束标记时,span出栈,span时div的子女;当遇到div的结束标记时,div出栈,表明div和它的子女都已经处理完毕,以此类推。

5. 线程化的解释器

顾名思义,线程化的解释器就是利用单独的线程来解释HTML文档。因为在Webkit中,网络资源的字节流自IO线程传递给渲染线程之后,后面的解释、布局和渲染等工作基本上都是工作在该线程,也就是渲染线程完成的。因为DOM树只能在渲染线程上创建和访问,这就是说构建DOM树的过程只能在渲染线程中进行。但是,从字符串到词语这个阶段可交给单独的线程来做,Chromium就是这个思想。

6. JavaScript的执行

在HTML解释器工作过程中,可能会有JavaScript代码需要执行,它发生在将字符串解释成词语之后、创建各种节点的时候。这也是为什么全局执行的JavaScript代码不能访问DOM树的原因–因为DOM树还没有被创建完成。
Webkit将DOM树创建过程中需要执行的JavaScript代码交给HTMLScriptRunner类来执行。工作方式很简单,就是利用JavaScript引擎来执行Node节点中包含的代码。
因为JavaScript代码可能会调用例如document。write()来修改文档结构,所以JavaScript代码的执行会阻塞后面节点的创建,同时当然也会阻塞后面资源的下载,这时候Webkit对需要什么资源一无所知,这导致了资源不能够并发的下载这种性能问题。所以使用JavaScript的时候有以下两点优化建议:

  1. script标签加上async属性,表明这是一个可以异步执行的代码;
  2. script标签放到body元素的最后,也就是之前。

此外,针对资源不能够并发的下载这种性能问题,Webkit使用预扫描和预加载机制来解决。具体的做法是:当遇到需要执行JavaScript代码的时候,Webkit先暂停当前JavaScript代码的执行,使用预扫描器来扫描后面的词语,如果发现需要使用其他资源,就会使用预加载器发送请求获取资源,在这之后,才开始执行JavaScript代码。预扫描器本身并不难创建节点,也不会创建DOM树,所以速度比较快。就是如此,还是建议使用上面的两点优化建议。

当DOM树构建完之后,Webkit触发DOMContentLoaded事件,注册在该事件上的JavaScript函数会被调用。当所有资源都被加载完之后,Webkit会触发onload事件。

你可能感兴趣的:(webkit,前端开发)