From URL to Interactive(二)---从标签到DOM(Tags to DOM)

这是《From URL to Interactive》系列文章的第一篇《Server to Client》。《From URL to Interactive》是个引子就不译了,文章主要基于windows自带的浏览器Eage为基础介绍现代浏览器对HTML从请求、链接、加载、解析、渲染、交互的过程。分阶段介绍:

From URL to Interactive(一)---从服务器到客户端(Server to Client)

From URL to Interactive(二)---从标签到DOM(Tags to DOM

From URL to Interactive(三)---从大括号到像素(Braces to Pixels)

From URL to Interactive(四)---从var到及时编译(Var to JIT)

 

篇原文地址:https://alistapart.com/article/tags-to-dom

-----------------------------------------------------------------------------------------------------------------------------

在我们之前的“ 服务器到客户端 ” 部分中,我们了解了如何向服务器请求URL并了解有助于优化相关资源交付的许多条件和缓存。一旦浏览器引擎最终获得资源,它就需要开始将其转换为呈现的网页。在这一部分中,我们主要关注HTML资源,以及如何将HTML标签(tags)转换为最终将在屏幕上呈现的构建块。

解析

一旦内容通过网络系统从服务器传送到客户端,其第一站就是HTML解析器,它由一些协同工作的系统组成:编码,预解析,标记化和树构造。解析器是建筑项目隐喻的一部分,我们在其中浏览所有原材料:拆包; 卸载托盘、管道、电线等; 在把所有东西都交给那些从事框架,管道,电气等工作的专家之前,先浇一下粉底

编码

HTTP响应主体(response body)的有效负载可以是从HTML文本到图像数据的任何内容。解析器的第一项工作是弄清楚如何解释刚从服务器接收的bits(数据流)。假设我们正在处理HTML文档,解码器必须知道如何将文本文档转换为bits,从而能反转该过程(即由bits还原文本)。

(请记住,最终连文本都必须在计算机中翻译成二进制。编码(在里指ASCII编码)定义了二进制值,如“01000100”表示字母“D”,如上图所示。)对于文本有很多可选的编码 - 浏览器需要弄清楚如何正确解码文本。服务器应通过Content-Type 头信息提供提示,并且可以分析前导位(对于byte order mark或BOM)。如果仍然无法确定编码,则浏览器可以使用启发式做最可能的猜测。有时唯一明确的答案来自(编码的)内容本身的<meta>标签中。最糟糕的情况是,浏览器进行了有根据的猜测,然后发现了与解析的<meta>标签矛盾。在这种极少发生的情况下,解析器会丢弃以前解码的内容,重新启动。浏览器有时必须处理旧的Web内容(使用遗留编码),并且很多系统都适当的支持该能力(译者:指支持对旧编码解析)。

现在为Web保存HTML文档时,编码的选择已经很明确了:使用UTF-8编码。为什么?它很好地支持完整的Unicode字符范围,与CSS,HTML和JavaScript等语言常使用的单字节字符的ASCII编码具有良好的兼容性,UTF-8很可能是浏览器的反馈的默认值。您可以判断编码何时出错,因为错误的编码会让文本无法正确呈现(通常在清晰可见的文本中会看到垃圾字符或框)。

预解析/扫描

一旦编码已知,解析器就开始初始预解析步骤来扫描内容,预解析的目的是最小化其他资源的往返延迟。预解析器不是完整的解析器; 例如,它不了解HTML中的嵌套级别或父/子关系。但是,预解析器却可以识别特定的HTML标记名称和属性以及URL。例如,如果您<img src="https://somewhere.example.com/​images/​dog.png" alt="">的HTML内容中有某个位置,则预解析器会注意到该src属性,并通过网络系统将dog.png资源请求队列。这样可以尽快地请求dog.png,最大限度地减少等待它从网络到达所需的时间。预解析器还可以注意到HTML中的某些显式请求,例如preloadprefetch指令,并将它们放入处理队列。

TOKENIZATION

Tokenization是解析HTML的前半部分。它涉及将markup转换为单独的token,例如“开始标记”(begin tag),“结束标记”(end tag),“文本运行”(text run),“注释”(comment)等,这些token被送入解析器的下一个状态。tokenizer是一种状态机,它在HTML语言的不同状态之间转换,例如“在标记打开状态”(<|video controls>),“在属性名称状态”(<video con | trols>)和“在属性名称状态之后”(<video controls | >),迭代地这样做因为HTML标记文本文档中的每个字符都会被读取。

(在每个示例标记中,竖线“|”标明了tokenizer的位置。)

 

在HTML规范(见“12.2.5 Tokenization”)目前为tokenizer定义了80个独立的状态。tokenizer和parser非常适应:两者都可以处理任何文本内容并将其转换为HTML文档 - 即使文本中的代码不是有效的HTML。像这样的弹性的能力是使网络对于所有技能水平的开发人员都友好的功能之一。但是,tokenizer和parser的弹性的缺点是您可能无法始终获得预期的结果,这可能会导致一些细微的编程错误。(检查HTML验证器中的代码可以帮助您避免这样的错误。)

对于那些喜欢采用更加黑白的方法来标记语言正确性的人来说,浏览器内置了一种替代解析机制,可将任何故障视为灾难性故障(意味着任何故障都会导致内容无法呈现)。此语法分析模式使用XML规则来处理HTML,并且可以通过在发送给浏览器的文档中添加“application/xhtml+xml”的MIME type来实现(或任何在HTML namespace中使用的基于XML MIME类型的elements)。

浏览器可以将pre-parser和tokenization两步组合在一起作为优化。

解析/构建树

浏览器需要一个网页的内部(in-memory)表示,并且在DOM标准中,Web标准确切地定义了表示应该是什么形状。parser的职责是在前一步骤中获取由tokenizer创建的tokens,并以适当的方式创建对象并将其插入到文档对象模型(DOM)中(具体使用其状态机的23个独立状态 ;请参阅“12.2.6.4在HTML内容中解析标记的规则”)。DOM被组织成树型结构,因此该过程有时被称为构造树。(另外,Internet Explorer 在其历史的大部分时间里都没有使用树结构。)

HTML解析由于要进行各种错误处理而变得复杂,这些错误处理确保旧HTML内容在现代浏览器中具有兼容的结构。例如,许多HTML标记都隐含了结束标记,这意味着如果您不提供结束标记,浏览器会自动为您关闭匹配标记。例如,考虑这个HTML:

这样可以确保结果树中的两个段落对象是兄弟节点,而忽略第二个打开的标签。HTML表可能是解析器规则试图确保其具有合理结构的最复杂的对象。

尽管有复杂的解析规则,单一旦DOM树创建好,就不再强制执行创建“正确”HTML结构的规则。使用JavaScript,网页几乎可以以任何方式重新排列DOM树,即使它没有意义!(例如,添加表格单元格作为<video>标记的子级)。渲染系统负责弄清楚如何处理那些奇怪的不一致的地方。

HTML解析中的另一个复杂因素是JavaScript可以在解析器处于工作时添加更多要解析的内容。<script>标记包含解析器必须收集的文本,然后发送到脚本引擎进行评估。当脚本引擎解析并评估脚本文本时,解析器会等待。如果脚本评估包括调用document.writeAPI,第二个HTML解析器必须开始运行(reentrantly)。要快速重新审视我们的建筑比喻,<script>和document.write要求停止所有正在进行的工作,回到商店,以获得一些我们没有意识到所需要的其他材料。当我们离开商店后,施工的所有进展都将停滞不前。

所有这些复杂性使得编写兼容的HTML解析器成为一件非常重要的事情。

事件(Events)

当解析器完成时,它通过一个名为DOMContentLoaded的事件宣告它的完成。Events是浏览器内置的广播系统通过JavaScript能监听和响应的。在我们的构造比喻中,事件是各种工人在遇到问题或完成任务时带给工头的报告。比如DOMContentLoaded,有各种各样的event表明网页中的重大状态变化,例如load(意味着解析完成,解析器请求的所有资源,如图像,CSS,视频等,已经下载)和unload(意味着网页即将关闭)。许多事件是特定于用户输入,例如用户触摸屏幕(pointerdown,pointerup,及其他),使用鼠标(mouseover,mousemove和其他),或在键盘上输入(keydown,keyup,和keypress)。

浏览器在DOM中创建一个事件对象,其中包含大量有用的状态信息(例如屏幕上触摸的位置,按下的键盘上的键等等),并“触发”该事件。然后运行恰好正在侦听该事件的JavaScript代码并将事件对象一起提供给该JavaScript。

DOM的树型结构方便的让树中的任何级别(如,树的根,树的叶子,以及他们之间的任何地方)可以通过监听event来过滤(“filter”)频繁的event响应(译者:即对event的选择性处理,因为root下的子节点event都会反映到根部,所以这里讲频繁的event的响应)。浏览器首先确定树中触发事件的位置(意味着哪个DOM对象,例如特定<input>控件),然后计算从树的根开始的事件的路由,然后向下遍历每个分支,直到它到达目标(例如<input>),然后沿着相同的路径返回到根。然后沿着路径的每个对象触发其事件侦听器,这使得树的根处的侦听器将“看到”比叶子处的特定侦听器更多的事件。

 

某些事件也可以取消,例如,如果表单未正确填写,则可以提供停止表单提交的功能。(submit从<form>元素触发事件, JavaScript侦听器可以检查表单,并且如果字段为空或无效,则可以选择取消事件。)

DOM

HTML语言提供了丰富的功能集,这些扩展远远超出了解析器处理的范围。解析器构建了包含元素之间的包含关系,以及元素的初始状态(属性)的结构。这些结构和状态的组合,就足以提供基本的渲染和一些交互能力(如通过像<textarea>,<video>,<button>等等这样的内置控件)。但是如果没有添加CSS和JavaScript,网络将非常枯燥(和静态)。DOM为HTML元素和与HTML无关的其他对象提供了额外的功能层。

在建筑比喻中,解析器组装了最终的建筑物 - 所有的墙壁,门,地板和天花板都已安装完毕,管道,电气,天然气等都已准备就绪。你可以打开门窗,打开和关闭灯,但结构非常简单。例如,CSS在墙壁和踢脚线上提供内部细节颜色。(我们将在下一部分中介绍CSS。)JavaScript允许访问DOM - 内部的所有家具和设备,以及建筑物外的服务,例如邮箱,存储棚和工具,太阳能电池板,水好吧等我们接下来描述“家具”和外面的“服务”。

元素接口

当解析器构造要放入树中的对象时,它会查找元素的名称(和命名空间)并找到匹配的HTML接口来围绕对象。

接口为基本的HTML元素添加特定于其类型或元素类型的特性。一些通用特性包括:

  1. 访问代表元素子元素的全部或子集的HTML集合;
  2. 搜索元素的属性,子元素和父元素的能力;
  3. 而且重要的是,创建新元素的方法(不使用解析器),并将它们附加到树上(或从中移除)。

对于特定元素,比如<table>,该接口包含用于查找表中所有行,列和单元格的其他特定于表的功能,以及用于从表中删除和添加行和单元格的快捷方式。同样,<canvas>界面具有绘制线条,形状,文本和图像的功能。使用这些API仅靠HTML标记是不行的,需要用到JavaScript。

在解析结束后,通过上述API对树进行的任何DOM更改(例如树中元素的层次结构位置,通过切换属性名称或值的元素状态,或来自元素接口的任何API操作)将触发浏览器系统的连锁反应,其工作是分析更改并尽快更新你在屏幕上看到的内容。为了使这些重复更新快速有效,该DOM树保持了许多优化,例如:

  1. 通过数字表示公共元素名称和属性(使用哈希表进行快速识别);
  2. 记录元素经常访问的子节点集合的缓存(用于快速子元素迭代);
  3. 子树更改跟踪,以最小化整个树的哪些部分变得“脏”(并且需要重新验证)

其他API

DOM中的HTML元素及其接口是浏览器在屏幕上显示内容的唯一机制。CSS可以影响布局,但仅限于HTML元素中存在的内容。最终,如果你想在屏幕上看到内容,它必须通过作为树的一部分的HTML接口来完成。“(对于那些想知道可缩放矢量图形(SVG)和MathML语言的人 - 这些元素也必须添加到树中-为了简洁我这里将跳过它们。)

我们学习了解析器是如何将HTML从服务器获取到DOM树中的一种方式,以及DOM中的元素接口如何用于在事后添加,删除和修改该树。然而,浏览器的可编程DOM非常庞大,并不仅限于HTML元素接口。

浏览器的DOM范围可与应用程序在任何操作系统中使用的功能集相媲美。比如下面这些(但不限于)事情:

  1. 访问存储系统(数据库,key/value存储,网络缓存存储(network cache storage));
  2. 设备(各种类型的地理定位,距离和方向传感器,USB,MIDI,蓝牙,游戏手柄);
  3. 网络(HTTP交换,双向服务器套接字,实时媒体流);
  4. 图形(2D和3D图形基元,着色器,虚拟和增强现实);
  5. 和多线程(具有丰富消息传递功能的共享和专用执行环境)。

随着主要浏览器引擎开发和实施新的Web标准,DOM公开的功能不断增加。然而,DOM的这些“额外”API中的大多数都超出了本文的范围。

继续看标记(Moving on From markup)

在这一部分中,您已经了解了解析和树构造如何为DOM创建基础:从网络接收的HTML标记的有状态内存表示。

有了DOM模型,诸如事件模型和元素API之类的服务使Web开发人员可以随时更改DOM结构。每次更改都会开始一系列“重建”工作,更新DOM只是第一步。

回到建筑类比,现场原材料已形成建筑物的结构框架,并按照正确的尺寸建造,安装了内部管道,电气和其他服务,但对建筑物的最终结构还没有真正的意义外观 - 外观和室内设计。

在下一部分中,我们将介绍浏览器如何将包含CSS的DOM树作为布局引擎(layout engine)的输入,并将树转换为最终可以在屏幕上看到的内容。

 

关于作者

Travis Leithead

Travis在Microsoft Edge Web平台上工作,专注于DOM API及其相关标准。他帮助协调其他人参与标准的指定,并担任W3C技术架构小组的成员,主要负责审查新Web标准的提案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章