构建对象模型

Ilya Grigorik
Ilya Grigorik

发布时间:2014 年 3 月 31 日

浏览器需要先构建 DOM 和 CSSOM 树,然后才能呈现网页。因此,我们需要确保尽快将 HTML 和 CSS 传送给浏览器。

摘要

  • 字节 → 字符 → 令牌 → 节点 → 对象模型。
  • HTML 标记会转换为文档对象模型 (DOM);CSS 标记会转换为 CSS 对象模型 (CSSOM)。
  • DOM 和 CSSOM 是独立的数据结构。
  • 借助 Chrome 开发者工具的“性能”面板,我们可以捕获和检查 DOM 和 CSSOM 的构建和处理开销。

文档对象模型 (DOM)

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

试试看

从最简单的场景开始:包含一些文字和一张图片的纯 HTML 网页。浏览器如何处理此页面?

DOM 构建流程

  1. 转换:浏览器从磁盘或网络读取 HTML 的原始字节,并根据文件的指定编码(例如 UTF-8)将其转换为各个字符。
  2. 令牌化:浏览器会将字符串转换为不同的令牌(如 W3C HTML5 标准中所指定的 <html><body>)以及其他尖括号内的字符串。每个令牌都有特殊含义和自己的一组规则。
  3. 词法分析:发出的令牌会转换为“对象”,用于定义其属性和规则。
  4. DOM 构建:最后,由于 HTML 标记定义了不同标记之间的关系(某些标记包含在其他标记中),因此创建的对象会以树状数据结构的形式关联起来,该结构还会捕获原始标记中定义的父子关系:HTML 对象是 body 对象的父级,bodyparagraph 对象的父级,以此类推,直到构建文档的完整表示形式。

DOM 树

整个过程的最终输出是简单网页的文档对象模型 (DOM),浏览器会使用该模型对网页进行所有后续处理。

每次浏览器处理 HTML 标记时,都会完成之前定义的所有步骤:将字节转换为字符、识别令牌、将令牌转换为节点,以及构建 DOM 树。整个过程可能需要一些时间,尤其是在我们有大量 HTML 需要处理时。

在开发者工具中跟踪 DOM 构建

如果您打开 Chrome 开发者工具并在网页加载时记录时间轴,则可以看到执行此步骤的实际时间。在上例中,我们将一段 HTML 转换为 DOM 树大约需要 5 毫秒。对于较大的网页,此过程可能需要更长时间。在创建流畅的动画时,如果浏览器必须处理大量 HTML,这可能会成为瓶颈。

DOM 树会捕获文档标记的属性和关系,但无法告诉我们元素在呈现时的外观。这是 CSSOM 的职责。

CSS 对象模型 (CSSOM)

在构建基本网页的 DOM 时,浏览器在文档的 <head> 中遇到了引用外部 CSS 样式表 style.css<link> 元素。由于预计需要此资源才能呈现页面,因此它会立即发出对此资源的请求,该请求会返回以下内容:

body {
  font-size: 16px;
}

p {
  font-weight: bold;
}

span {
  color: red;
}

p span {
  display: none;
}

img {
  float: right;
}

我们本可以直接在 HTML 标记中声明样式(内嵌),但让 CSS 与 HTML 保持独立有助于我们将内容和设计视为不同的问题:设计师可以处理 CSS,开发者可以专注于 HTML 以及其他问题。

与 HTML 一样,我们需要将收到的 CSS 规则转换为浏览器能够理解和使用的格式。因此,我们重复 HTML 流程,但使用 CSS 而不是 HTML:

CSSOM 构建步骤

CSS 字节会转换为字符,然后转换为令牌,然后转换为节点,最后它们会关联到一个称为“CSS 对象模型”(CSSOM) 的树结构:

CSSOM 树

为什么 CSSOM 采用树结构?在为网页上的任何对象计算最终的一组样式时,浏览器会从适用于该节点的最通用规则开始(例如,如果它是 body 元素的子元素,则应用所有 body 样式),然后通过应用更具体的规则递归地优化计算出的样式;也就是说,规则会“向下级联接”。

为了更具体地说明这一点,请考虑之前介绍的 CSSOM 树。放置在 body 元素中的 <span> 标记中包含的任何文本的字体大小为 16 像素,并且文本为红色 - font-size 指令从 body 级联到 span 级。不过,如果 span 是段落 (p) 标记的子标记,则其内容不会显示。

另请注意,之前介绍的树不是完整的 CSSOM 树,它仅显示我们决定在样式表中替换的样式。每款浏览器都会提供一组默认样式(也称为“用户代理样式”);如果我们不提供任何自己的样式,就会看到这些默认样式;我们的样式会替换这些默认样式。

如需了解 CSS 处理所需的时间,您可以在 DevTools 中记录时间轴,然后查找“重新计算样式”事件:与 DOM 解析不同,时间轴不会显示单独的“解析 CSS”条目,而是会捕获解析和 CSSOM 树构建,以及在此事件下的计算样式的递归计算。

在开发者工具中跟踪 CSSOM 构建

我们琐碎的样式表需要大约 0.6 毫秒的时间来处理,并会影响页面上的 8 个元素,虽然影响不大,但再次提醒您,这并非免费的。不过,这八大元素是从何而来的?CSSOM 和 DOM 是独立的数据结构! 事实证明,浏览器隐藏了一个重要步骤。接下来,我们将介绍渲染树,它将 DOM 和 CSSOM 相关联。

反馈