摘要:在这些罕见的情况下,解析器必须重新启动,丢弃之前解码的内容。标签包含解析器必须收集的文本,然后发送到脚本引擎进行评估。如果文件内调用了,解析器将重新开始解析过程。事件当解析器完成时,它通过一个名为的事件宣布完成。
浏览器基本的工作流程
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!
进入主话题之前,先罗列一下浏览器的主要构成:
用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
浏览器引擎- 用来查询及操作渲染引擎的接口
渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
JS解释器- 用来解释执行JS代码
数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术
解析当浏览器获得了资源以后要进行的第一步工作就是 HTML 解析,,它由几个步骤组成:编码、预解析、标记和构建树。
编码HTTP 响应主体的有效负载可以是从HTML文本到图像数据的任何内容。解析器的第一项工作是找出如何转制刚刚从服务器接收到的 bit。
假设我们正在处理一个HTML文档,解码器必须弄清楚文本文档是如何被转换成比特(bit)的,以便反转这个过程。
记住,最终即使是文本也会被计算机翻译成二进制,如上图所示,在本例中是 ASCII 编码—定义二进制值,如“01000100”表示字母“D”。
对于文本存在许多可能的编码—浏览器的工作是找出如何正确地解码文本。服务器应该通过 Content-Type 提供的信息同时在文本文件头部使用 Byte Order Mark 告知浏览器编码格式。
如果仍然无法确定编码,浏览器还会自行匹配一种解码格式来处理数据。有时候,解码格式也会写在 标签中。
最坏的情况是,浏览器进行了有根据的猜测,然后开始解析之后发现一个矛盾的 标签。在这些罕见的情况下,解析器必须重新启动,丢弃之前解码的内容。浏览器有时必须处理旧的 web内容(使用遗留编码),许多这样的系统都支持这一点。
我们现在经常在 HTML中使用的文件格式是 UTF-8,那是因为 UTF-8 能较完整的支持Unicode 字符范围,同时与 CSS、JavaScript 中常见的节字符具有良好的 ASCII 兼容性。一般浏览器默认的解码格式也是 UTF-8。当解码出错的时候,我们会看到屏幕上全部都是乱码字符。
预解析在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。
预解析器不是完整的解析器,如,它不理解 HTML 中的嵌套级别或父/子关系。但是,预解析可以识别特定的 HTML 标签的名称和属性,以及 URL。例如,如果你的 HTML 内容中有一个 ,预解析将注意到src属性,并将获取这个图片的请求加到请求队列中。
请求图片的速度越快越好,将等待它从网络到达的时间降到最低。预解析还会注意到 HTML 中的某些显式请求,比如 preload 和 prefetch 指令,并将它们加入等待队友中进行处理。
标记化(Tokenization)该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。该算法相当复杂,无法在此详述,所以我们通过一个简单的示例来帮助大家理解其原理。
基本示例 - 将下面的 HTML 代码标记化:
Hello world
初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。接收一个 a-z 字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。在本例中,我们创建的标记是 html 标记。
遇到 > 标记时,会发送当前的标记,状态改回“数据状态”。
标记也会进行同样的处理。目前 html 和 body 标记均已发出。现在我们回到“数据状态”。接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 中的 <。我们将为 Hello world 中的每个字符都发送一个字符标记。现在我们回到“标记打开状态”。接收下一个输入字符 / 时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。