Skip to content

DOM 导航

HTML 文档对象模型 (DOM) 将 HTML 文档表示为节点构成的树状结构。JavaScript 允许你导航这个树,访问并操作其中的元素、属性和内容。

根据 W3C HTML DOM 标准,HTML 文档中的一切都是节点:

  • 整个文档是一个文档节点(document)。
  • 每个 HTML 元素(例如,<h1>、<p>、<div>)是一个元素节点。
  • HTML 元素内部的实际文本内容是文本节点。
  • 每个 HTML 属性(例如,<a> 中的 href,<img> 中的 src)是一个属性节点(尽管通常通过元素属性访问)。
  • 注释(<!-- ... -->)是注释节点。

想象一个 HTML 文档是一棵家谱树。<html> 元素是根。像 <head> 和 <body> 这样的元素是它的子代。嵌套更深的元素是后代。这种树状结构允许 JavaScript 遍历并与文档的任何部分进行交互。

通过 HTML DOM,JavaScript 可以:

  • 查找、获取、改变、添加或删除 HTML 元素。
  • 改变 HTML 属性。
  • 改变 CSS 样式。
  • 响应 HTML 事件。

DOM 树中的节点具有层级关系:

  • 根节点:树中最顶层的节点(通常是 HTML 文档中的 <html> 元素,或 document 节点本身)。
  • 父节点:每个节点(根节点除外)都有且只有一个父节点。
  • 子节点:一个节点可以有零个、一个或多个子节点。
  • 同级节点:共享同一个父节点的节点是同级节点。
  • 后代节点:一个节点的子节点、子节点的子节点,依此类推。
  • 祖先节点:一个节点的父节点、父节点的父节点,依此类推,直到根节点。

考虑这个简单的 HTML 结构:

<html>
<head>
<title>DOM Tutorial</title>
</head>
<body>
<h1>DOM Lesson One</h1>
<p>Hello world!</p>
</body>
</html>

从此结构中可知:

  • <html> 是根元素节点。
  • <html> 是 <head> 和 <body> 的父节点。
  • <head> 和 <body> 是 <html> 的同级节点和子节点。
  • <title> 是 <head> 的子节点。
  • 文本 “DOM Tutorial” 是一个文本节点,也是 <title> 的子节点。
  • <h1> 和 <p> 是 <body> 的子节点,也是同级节点。

JavaScript 提供了基于这些关系来导航 DOM 树的属性:

包含空白/文本/注释节点的属性:

Section titled “包含空白/文本/注释节点的属性:”
  • parentNode:访问当前节点的父节点。
  • childNodes:返回一个 NodeList(集合),包含所有子节点,包括文本节点(空白)和注释节点。
  • firstChild:返回第一个子节点(可以是文本、注释或元素节点)。
  • lastChild:返回最后一个子节点(可以是文本、注释或元素节点)。
  • nextSibling:返回树中同一层级的下一个节点(可以是文本、注释或元素节点)。
  • previousSibling:返回树中同一层级的上一个节点(可以是文本、注释或元素节点)。

专注于元素节点的属性(通常更方便):

Section titled “专注于元素节点的属性(通常更方便):”
  • parentElement:类似于 parentNode,但如果父节点不是元素节点,则返回 null。
  • children:返回一个 HTMLCollection,只包含子元素节点(忽略文本节点和注释节点)。
  • firstElementChild:返回第一个子元素节点。
  • lastElementChild:返回最后一个子元素节点。
  • nextElementSibling:返回下一个元素同级节点。
  • previousElementSibling:返回上一个元素同级节点。
常见陷阱:childNodes、firstChild、lastChild、nextSibling 和 previousSibling 可能会返回文本节点(通常只是 HTML 源代码中元素之间的空白)或注释节点。如果你特别需要元素节点,请使用它们的 Element 对应属性,如 children、firstElementChild 等。这可以帮助你避免意外的错误或行为。

访问节点内容:textContent 和 innerHTML

Section titled “访问节点内容:textContent 和 innerHTML”

一个常见的错误是期望元素节点(例如,<p>我的文本</p>)直接包含其文本。元素节点 <p> 实际上包含一个文本节点,其值是 “My Text”。

要获取或设置元素及其后代的文本内容:

  • textContent:获取或设置一个节点及其后代的文本内容。对于处理文本而言,它通常更安全且更快,因为它不会解析 HTML。
  • innerHTML:获取或设置元素内包含的 HTML 标记。从不可信来源设置内容时请谨慎使用,因为存在潜在的 XSS 安全风险。它会重新解析内容,这可能会比较慢。

nodeValue 属性可用于获取或设置某些节点类型的值,主要是文本节点和注释节点。对于元素节点,nodeValue 为 null。

使用 firstElementChild 和 textContent 的示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM Navigation Example</title>
</head>
<body>
<div id="container">
<h1>My First Page</h1>
<p id="intro">This is the introduction.</p>
</div>
<p id="demo">Result will appear here.</p>
<script>
const containerDiv = document.getElementById("container");
const firstHeading = containerDiv.firstElementChild; // 获取 <h1>
const demoP = document.getElementById("demo");
if (firstHeading && demoP) {
// 使用 textContent 从 h1 获取文本
demoP.textContent = "The first element's text: " + firstHeading.textContent;
}
// 使用 childNodes 和 nodeValue 从 p#intro 获取文本(更详细)
// const introP = document.getElementById("intro");
// if (introP && introP.firstChild && introP.firstChild.nodeType === Node.TEXT_NODE) {
// console.log("Intro 文本节点的值:", introP.firstChild.nodeValue);
// }
</script>
</body>
</html>

在此示例中,document.getElementById() 是一个用于查找元素的方法。firstElementChild 和 textContent 是属性。

JavaScript 提供对文档关键部分的直接访问:

  • document.documentElement:表示文档的根元素(通常是 <html> 标签)。
  • document.body:表示文档的 <body> 元素。
  • document.head:表示文档的 <head> 元素。

示例:弹窗显示 body 的 HTML 内容。

/*
// 这将在一个弹窗中显示 body 的整个 HTML 内容。
// 弹窗显示大量 HTML 时请谨慎,因为它可能难以阅读。
if (document.body) {
alert(document.body.innerHTML);
} else {
// 如果脚本在 body 解析之前运行且未使用 defer/DOMContentLoaded,可能会发生这种情况
console.error("document.body 尚未准备好。");
}
*/

只读属性 nodeName 指定了节点的名称:

  • 对于元素节点,它是大写的标签名(例如,“DIV”、“P”、“H1”)。
  • 对于属性节点,它是属性名。
  • 对于文本节点,它总是 “#text”。
  • 对于注释节点,它总是 “#comment”。
  • 对于文档节点,它总是 “#document”。
const myDiv = document.createElement('div');
console.log(myDiv.nodeName); // 输出: "DIV"

只读属性 nodeType 返回一个数字,代表节点的类型。这对于检查你正在处理的节点类型非常有用。

关键的 nodeType 值:

节点类型描述NodeType 值常量 (例如,Node.ELEMENT_NODE)
元素1Node.ELEMENT_NODE
属性2Node.ATTRIBUTE_NODE
文本3Node.TEXT_NODE
注释8Node.COMMENT_NODE
文档9Node.DOCUMENT_NODE
文档片段11Node.DOCUMENT_FRAGMENT_NODE
const pElement = document.querySelector('p');
if (pElement && pElement.firstChild && pElement.firstChild.nodeType === Node.TEXT_NODE) {
// 如果 pElement 存在,并且它的第一个子节点存在且其类型为 Node.TEXT_NODE
console.log("第一个子节点是文本节点。");
} else if (pElement && pElement.firstChild && pElement.firstChild.nodeType === Node.ELEMENT_NODE) {
// 否则,如果 pElement 存在,并且它的第一个子节点存在且其类型为 Node.ELEMENT_NODE
console.log("第一个子节点是元素节点。");
}