DOM 简介

文档对象模型 (DOM) 是构成网页结构和内容的对象的数据表示。本指南将介绍 DOM,了解 DOM 如何在内存中表示 HTML 文档,以及如何使用 API 创建 Web 内容和应用程序。

什么是 DOM?

文档对象模型 (DOM) 是 Web 文档的编程接口。它表示页面,以便程序可以更改文档结构、样式和内容。DOM 将文档表示为节点和对象;这样,编程语言就可以与页面交互。

网页是可以显示在浏览器窗口或作为 HTML 源代码的文档。在这两种情况下,都是同一个文档,但文档对象模型 (DOM) 表示允许对其进行操作。作为网页的面向对象的表示,它可以使用 JavaScript 等脚本语言进行修改。

例如,DOM 指定此代码片段中的 querySelectorAll 方法必须返回文档中所有 <p> 元素的列表

js
const paragraphs = document.querySelectorAll("p");
// paragraphs[0] is the first <p> element
// paragraphs[1] is the second <p> element, etc.
alert(paragraphs[0].nodeName);

所有用于操作和创建网页的属性、方法和事件都组织成对象。例如,表示文档本身的 document 对象,实现 HTMLTableElement DOM 接口以访问 HTML 表格的任何 table 对象,等等,都是对象。

DOM 是使用多个协同工作的 API 构建的。核心 DOM 定义了描述任何文档及其内部对象的实体。根据需要,其他 API 通过向 DOM 添加新功能和能力来扩展它。例如,HTML DOM API 向核心 DOM 添加了对表示 HTML 文档的支持,而 SVG API 添加了对表示 SVG 文档的支持。

DOM 和 JavaScript

前面的简短示例(就像几乎所有示例一样)是 JavaScript。也就是说,它是用 JavaScript 编写的,但使用 DOM 访问文档及其元素。DOM 不是编程语言,但如果没有它,JavaScript 语言就不会有对网页、HTML 文档、SVG 文档及其组成部分的模型或概念。整个文档、头部、文档中的表格、表格标题、表格单元格中的文本以及文档中的所有其他元素都是该文档的文档对象模型的一部分。可以使用 DOM 和 JavaScript 等脚本语言访问和操作它们。

DOM 不是 JavaScript 语言的一部分,而是用于构建网站的 Web API。JavaScript 也可用于其他环境。例如,Node.js 在计算机上运行 JavaScript 程序,但提供了一组不同的 API,而 DOM API 不是 Node.js 运行时的核心部分。

DOM 的设计独立于任何特定的编程语言,从而使文档的结构表示可以通过单个一致的 API 获得。即使大多数 Web 开发人员只会通过 JavaScript 使用 DOM,也可以为任何语言构建 DOM 的实现,正如这个 Python 示例所示

python
# Python DOM example
import xml.dom.minidom as m
doc = m.parse(r"C:\Projects\Py\chap1.xml")
doc.nodeName # DOM property of document object
p_list = doc.getElementsByTagName("para")

有关在 Web 上编写 JavaScript 所涉及的技术的更多信息,请参阅 JavaScript 技术概述.

访问 DOM

您无需执行任何特殊操作即可开始使用 DOM。您可以直接在称为脚本(浏览器运行的程序)的 JavaScript 中使用 API。

当您创建脚本时,无论是将其内联在 <script> 元素中还是将其包含在网页中,您都可以立即开始使用 API 操作 documentwindow 对象,以操作文档本身或网页中的任何元素(文档的子元素)。您的 DOM 编程可能像以下示例一样简单,该示例使用 console.log() 函数在控制台中显示一条消息

html
<body onload="console.log('Welcome to my home page!');"></body>

由于通常不建议将页面的结构(用 HTML 编写)与 DOM 的操作(用 JavaScript 编写)混合在一起,因此 JavaScript 部分将在此处分组,并与 HTML 分开。

例如,以下函数创建一个新的 h1 元素,向该元素添加文本,然后将其添加到文档的树中

html
<html lang="en">
  <head>
    <script>
      // run this function when the document is loaded
      window.onload = () => {
        // create a couple of elements in an otherwise empty HTML page
        const heading = document.createElement("h1");
        const headingText = document.createTextNode("Big Head!");
        heading.appendChild(headingText);
        document.body.appendChild(heading);
      };
    </script>
  </head>
  <body></body>
</html>

基本数据类型

此页面尝试用简单的术语描述各种对象和类型。但是,您应该了解 API 中传递的几种不同的数据类型。

注意:由于绝大多数使用 DOM 的代码都围绕着操作 HTML 文档,因此通常将 DOM 中的节点称为元素,尽管严格来说,并非每个节点都是元素。

下表简要描述了这些数据类型。

数据类型 (接口) 描述
Document 当成员返回类型为 document 的对象时(例如,元素的 ownerDocument 属性返回其所属的 document),此对象是根 document 对象本身。DOM document 参考 章节描述了 document 对象。
Node 文档中的每个对象都是某种类型的节点。在 HTML 文档中,对象可以是元素节点,也可以是文本节点或属性节点。
Element element 类型基于 node。它指的是由 DOM API 的成员返回的元素或类型为 element 的节点。与其说,例如,document.createElement() 方法返回对 node 的对象引用,不如说此方法返回刚刚在 DOM 中创建的 elementelement 对象实现 DOM Element 接口,也实现更基本的 Node 接口,两者都包含在此参考中。在 HTML 文档中,元素由 HTML DOM API 的 HTMLElement 接口以及描述特定类型元素能力的其他接口(例如,HTMLTableElement 用于 <table> 元素)进一步增强。
NodeList nodeList 是一个元素数组,例如由 document.querySelectorAll() 方法返回的数组。nodeList 中的项目可以通过以下两种方式之一按索引访问
  • list.item(1)
  • list[1]
这两种方式是等效的。在第一个中,item()nodeList 对象上的唯一方法。后者使用典型的数组语法来获取列表中的第二个项目。
Attr 当成员返回 attribute 时(例如,由 createAttribute() 方法返回),它是一个对象引用,它公开了一个用于属性的特殊(尽管很小)接口。属性是 DOM 中的节点,就像元素一样,尽管您可能很少将它们用作节点。
NamedNodeMap namedNodeMap 类似于数组,但项目可以通过名称或索引访问,尽管后者只是为了枚举的方便,因为它们在列表中没有特定的顺序。namedNodeMap 有一个 item() 方法用于此目的,您也可以向 namedNodeMap 添加和删除项目。

还有一些常见的术语考虑因素需要牢记。例如,通常将任何 Attr 节点称为 attribute,将 DOM 节点数组称为 nodeList。您会发现这些术语和其他将在整个文档中介绍和使用。

DOM 接口

本指南是关于您用来操作 DOM 层次结构的对象和实际事物。在许多方面,理解这些工作方式可能会令人困惑。例如,表示 HTML form 元素的对象从 HTMLFormElement 接口获取其 name 属性,但从 HTMLElement 接口获取其 className 属性。在这两种情况下,您想要的属性都在该表单对象中。

但对象与其在 DOM 中实现的接口之间的关系可能会令人困惑,因此本节尝试简要介绍 DOM 规范中的实际接口及其可用方式。

接口和对象

许多对象实现了多个不同的接口。例如,表格对象实现了专门的 HTMLTableElement 接口,其中包含诸如 createCaptioninsertRow 等方法。但由于它也是一个 HTML 元素,因此 table 实现了 DOM Element 参考章节中描述的 Element 接口。最后,由于 HTML 元素在 DOM 的眼中也是构成 HTML 或 XML 页面对象模型的节点树中的一个节点,因此表格对象也实现了更基本的 Node 接口,而 Element 则派生自该接口。

当您获得对 table 对象的引用时,例如在以下示例中,您通常会在对象上交替使用所有这三个接口,也许您自己并不知道。

js
const table = document.getElementById("table");
const tableAttrs = table.attributes; // Node/Element interface
for (let i = 0; i < tableAttrs.length; i++) {
  // HTMLTableElement interface: border attribute
  if (tableAttrs[i].nodeName.toLowerCase() === "border") {
    table.border = "1";
  }
}
// HTMLTableElement interface: summary attribute
table.summary = "note: increased border";

DOM 中的核心接口

本节列出了 DOM 中一些最常用的接口。其目的不是在这里描述这些 API 的功能,而是让您了解在使用 DOM 时经常会遇到的方法和属性类型。这些通用 API 在本书结尾的 DOM 示例 章节中更长的示例中使用。

documentwindow 对象是您在 DOM 编程中通常最常使用的接口对象。简单来说,window 对象代表类似浏览器的东西,而 document 对象是文档本身的根节点。Element 继承自通用 Node 接口,这两个接口一起提供了您在单个元素上使用的许多方法和属性。这些元素也可能具有特定于处理这些元素所持数据类型的接口,如上一节中 table 对象的示例。

以下是使用 DOM 进行 Web 和 XML 页面脚本编写的一些常见 API 的简要列表。

示例

设置文本内容

此示例使用一个包含 <textarea> 和两个 <button> 元素的 <div> 元素。当用户单击第一个按钮时,我们在 <textarea> 中设置一些文本。当用户单击第二个按钮时,我们清除文本。我们使用

HTML

html
<div class="container">
  <textarea class="story"></textarea>
  <button id="set-text" type="button">Set text content</button>
  <button id="clear-text" type="button">Clear text content</button>
</div>

CSS

css
.container {
  display: flex;
  gap: 0.5rem;
  flex-direction: column;
}

button {
  width: 200px;
}

JavaScript

js
const story = document.body.querySelector(".story");

const setText = document.body.querySelector("#set-text");
setText.addEventListener("click", () => {
  story.textContent = "It was a dark and stormy night...";
});

const clearText = document.body.querySelector("#clear-text");
clearText.addEventListener("click", () => {
  story.textContent = "";
});

结果

添加子元素

此示例使用一个包含 <div> 和两个 <button> 元素的 <div> 元素。当用户单击第一个按钮时,我们创建一个新元素并将其作为 <div> 的子元素添加。当用户单击第二个按钮时,我们删除子元素。我们使用

HTML

html
<div class="container">
  <div class="parent">parent</div>
  <button id="add-child" type="button">Add a child</button>
  <button id="remove-child" type="button">Remove child</button>
</div>

CSS

css
.container {
  display: flex;
  gap: 0.5rem;
  flex-direction: column;
}

button {
  width: 100px;
}

div.parent {
  border: 1px solid black;
  padding: 5px;
  width: 100px;
  height: 100px;
}

div.child {
  border: 1px solid red;
  margin: 10px;
  padding: 5px;
  width: 80px;
  height: 60px;
  box-sizing: border-box;
}

JavaScript

js
const parent = document.body.querySelector(".parent");

const addChild = document.body.querySelector("#add-child");
addChild.addEventListener("click", () => {
  // Only add a child if we don't already have one
  // in addition to the text node "parent"
  if (parent.childNodes.length > 1) {
    return;
  }
  const child = document.createElement("div");
  child.classList.add("child");
  child.textContent = "child";
  parent.appendChild(child);
});

const removeChild = document.body.querySelector("#remove-child");
removeChild.addEventListener("click", () => {
  const child = document.body.querySelector(".child");
  parent.removeChild(child);
});

结果

规范

规范
DOM 标准