[译] 到底什么是DOM?

388 阅读5分钟

文档对象模型(Document Object Model),简称"DOM",是web页面的一种接口。它本质上是一种允许程序读取和操作页面的内容、结构、样式的API。让我们来逐步拆解它。

web页面是怎么构建的?

浏览器在视窗中将源HTML文档的内容显示成具有样式和交互的页面的过程叫做“关键渲染路径”(Critical Rendering Path)。这个过程可以分解成几个步骤,我在我的“理解关键路径渲染”,这一篇文章中有详细介绍,这些步骤大致可以分为两个阶段。第一个阶段是浏览器通过解析HTML文档来决定最终会渲染哪些元素在页面上,第二个阶段则是浏览器执行相关的渲染操作。

第一阶段完成后得到的结果称之为“渲染树”。渲染树表示的是渲染在页面上的HTML元素和它们对应的样式。为了构建出渲染树,浏览器需要两样东西:

  • CSSOM,与渲染元素相关的样式表现形式
  • DOM,渲染元素的表现形式 ·

DOM是怎样创建的(它看起来像是什么)?

DOM是源HTML文档基于对象的一种表现形式。但是它和HTML文档又有些不同,我们接下来会讲到,它尝试将HTML文档结构和内容转换成可以在不同的程序中使用的对象模型。

DOM对象的结构使用一种叫做“节点树(node tree)”的数据结构来表示。之所以称它为节点树,是因为它可以被看做是一根母茎上挂了很多子树枝,每条树枝上可以有自己的叶子节点的树。在DOM中,这个母茎就是根元素<html>,子树枝就是<html>标签的内嵌元素,叶子节点就是内嵌元素里面的内容。

来看一个HTML文档的例子:

<!doctype html>
<html lang="en">
 <head>
   <title>My first web page</title>
  </head>
 <body>
    <h1>Hello, world!</h1>
    <p>How are you?</p>
  </body>
</html>

这个HTML文档可以用下面这个节点树表示:

注: HTML标签称之为元素节点,元素内的文本形成文本节点,一个文本节点只包含一个字符串,没有子项,所以总是树的叶子。参考javascript现代教程

DOM元素不是什么

从我上面给出的这个例子来看,DOM和你在浏览器的开发者工具中看到的源HTML文档似乎是一对一的映射关系。但是我之前说过,它们有不同的地方。为了理解透彻DOM是什么,我们要先看看它不是什么。

DOM并不是你的源HTML文档

尽管DOM是从源HTML文档创建而来,但是它并不完全一样。在下面两种情况下DOM可以和源HTML文档不一样

  • 当HTML不合法的时候

DOM是合法HTML文档的一种接口。在创建DOM的过程中,浏览器可以校正一些不合法的HTML代码。

来看一下这个例子:

<!doctype html>
<html>
Hello, world!
</html>

这个文档缺少合法HTML所必须的<head><body>元素。如果我们看一下生成的DOM树,会看到它已经被修正了:

  • 当DOM被Javascript修改的时候 DOM除了用作一种查看HTML文档内容的接口外,也可以被修改。

比如,我们可以使用Javascript给DOM创建额外的节点。

var newParagraph = document.createElement("p");
var paragraphContent = document.createTextNode("I'm new!");
newParagraph.appendChild(paragraphContent);
document.body.appendChild(newParagraph);

这个操作修改的是DOM,不是HTML文档

DOM并不是你在浏览器里面看到的那个样子(你看到的是渲染树)

你在浏览器视口里面看到的是我提到过的渲染树,它是属于DOM和CSSOM的组合。真正把DOM树和渲染树区分开来的是,后者只包括最终渲染在浏览器视口上的内容。

因为渲染树只关心要渲染的元素,它排除了视觉上隐藏的元素。例如,元素上有display:none相关的样式。

<!doctype html>
<html lang="en">
  <head></head>
  <body>
    <h1>Hello, world!</h1>
    <p style="display: none;">How are you?</p>
  </body>
</html>

DOM树会包含<p>标签:

然而对于渲染树来说,它展现的是要能在浏览器视窗中看到的元素,所以会排除<p>

浏览器开发者工具里面看到的不是DOM树

渲染树和DOM树的差异很微小,因为开发者工具的元素检查器提供了最接近浏览器中的DOM。然而,开发者工具元素检查器还包含非DOM的信息。

最明显的例子就是CSS伪元素。尽管伪元素通过CSSOM的::before::after选择器以及渲染树来创建,但从技术上来讲并非是DOM的一部分。这是因为DOM是从源HTML元素上单独构建而来,并不包含应用在元素上的样式。

尽管伪元素真的不是DOM的一部分,但是我们的开发者工具还是能看到它。

这就是为什么伪元素不能被Javascript操控,因为它不是DOM的一部分。

小结

DOM是HTML元素的一种接口。浏览器使用它完成渲染操作的第一个步骤:决定哪些元素渲染在视窗上,同时可以使用Javascript来修改它的内容,结构和页面上的样式。

尽管它和源HTML文档的组成非常相似,但是DOM有以下几个不同点:

  • 它必须是合法的HTML
  • 它是一种能被Javascript修改的动态模型
  • 它不包含伪元素(例如:::after)
  • 它包含隐藏元素(例如: 带有display:none 的元素)