人人都会AI编程

13.1 DOM 节点树与节点类型

更新时间:2026-07-11

当浏览器加载一个 HTML 页面时,它会将标记文本解析成一棵由各种节点构成的树状结构,这棵树就是 DOM(文档对象模型)。理解 DOM 节点树的结构和节点类型,是操作页面内容、处理交互的基础。所有后续的节点查找、增删改操作,都是在这棵树上进行的。

13.1.1 什么是 DOM 节点树

HTML 文档中的每一个组成部分——标签、文本、注释、属性——都是树上的一个 节点(Node) 。这些节点按照文档的嵌套关系构成父子层级,形成一棵以 document 为根的树。比如下面这段简单的 HTML:

<!DOCTYPE html>
<html>
  <head>
    <title>示例页面</title>
  </head>
  <body>
    <h1>标题</h1>
    <p>这是一段文字。</p>
  </body>
</html>

浏览器会将它解析成如下结构的节点树(简化版):

document
 └── html
      ├── head
      │    └── title
      │         └── "示例页面"  (文本节点)
      └── body
           ├── h1
           │    └── "标题"
           └── p
                └── "这是一段文字。"

在这个结构中,htmlheadbody 的父节点;h1p 是兄弟节点;title 下面的文本“示例页面”是一个独立的文本节点,而不是标签的一部分。这种层级关系就是程序操作页面的唯一入口。

13.1.2 核心节点类型

所有的节点都实现了 Node 接口,并根据具体类型派生出不同的子接口。开发者最常接触的节点类型有以下几种:

1. 元素节点(Element Node)

  • 对应 HTML 标签,如 <div><p><span>
  • 节点类型值:1Node.ELEMENT_NODE)。
  • 提供了访问属性、子节点、样式等丰富的方法(通过 HTMLElement 等子接口)。
const h1 = document.querySelector('h1');
console.log(h1.nodeType);   // 1
console.log(h1.nodeName);   // "H1"(大写)

2. 文本节点(Text Node)

  • 元素中的纯文本内容,例如 <p>Hello</p> 中的 “Hello”。
  • 节点类型值:3Node.TEXT_NODE)。
  • 文本节点是元素节点的子节点,可以通过 nodeValuedata 获取和修改文本。
const text = h1.firstChild;
console.log(text.nodeType);  // 3
console.log(text.nodeValue); // "标题"

3. 属性节点(Attr Node)

  • 表示标签上的属性,如 id="main"class="active"
  • 节点类型值:2Node.ATTRIBUTE_NODE),但在 DOM4 之后属性节点不再独立存在于树中,而是作为元素节点的属性集合存在。
  • 在实际开发中,一般通过 element.getAttribute()element.setAttribute()element.idelement.className 属性来操作,很少直接创建 Attr 对象。

4. 注释节点(Comment Node)

  • HTML 中的注释 <!-- 注释内容 -->
  • 节点类型值:8Node.COMMENT_NODE)。
  • 在遍历节点时有时会意外遇到注释,需要注意过滤。

5. 文档节点(Document Node)

  • 整个 DOM 树的根节点,即 document 对象。
  • 节点类型值:9Node.DOCUMENT_NODE)。
  • document 是访问所有节点的入口,提供了 getElementByIdquerySelector 等查找方法。

6. 文档片段节点(DocumentFragment Node)

  • 一个轻量级的临时容器,用于批量操作节点。
  • 节点类型值:11Node.DOCUMENT_FRAGMENT_NODE)。
  • 当向 fragment 中添加子节点时,不会引发页面回流(reflow),最后一次性插入真实 DOM 树,可显著提升性能。常用在批量插入大量元素时。

13.1.3 节点之间的关系属性

在节点树中,节点之间有几种核心关系,JavaScript 提供了对应的属性来访问:

// 以 childNode 为例
const p = document.querySelector('p');

// 父节点
p.parentNode;        // 获取父节点(任何类型)
p.parentElement;     // 获取父元素节点(为 null 如果父节点不是元素)

// 子节点
p.childNodes;        // 所有子节点(包含文本、注释)— NodeList
p.children;          // 只有子元素节点 — HTMLCollection
p.firstChild;        // 第一个子节点(可能是文本)
p.lastChild;         // 最后一个子节点
p.firstElementChild; // 第一个子元素节点
p.lastElementChild;  // 最后一个子元素节点

// 兄弟节点
p.previousSibling;        // 前一个兄弟节点(可能是文本)
p.nextSibling;            // 后一个兄弟节点
p.previousElementSibling; // 前一个兄弟元素节点
p.nextElementSibling;     // 后一个兄弟元素节点

重要区别childNodes 包含所有节点类型(文本、注释等),而 children 仅包含元素节点。大多数情况下,我们操作的是元素节点,所以 childrenfirstElementChild 等方法更常用。但需要注意,换行和缩进也会生成文本节点,这是新手常遇到的坑。

13.1.4 判断节点类型与节点信息

  • node.nodeType:返回节点类型对应的整数(1 元素,3 文本,8 注释等)。
  • node.nodeName:返回节点名称,对元素来说就是大写的标签名,对文本节点是 "#text",注释是 "#comment"
  • node.nodeValue:对于文本和注释节点,返回或设置文本内容;对于元素节点,返回 null
const div = document.createElement('div');
div.innerHTML = 'Hello <!-- 注释 -->';
// div.childNodes:
// 0: 文本节点 "Hello "
// 1: 注释节点

for (let node of div.childNodes) {
  if (node.nodeType === Node.TEXT_NODE) {
    console.log('文本:', node.nodeValue);
  } else if (node.nodeType === Node.COMMENT_NODE) {
    console.log('注释:', node.nodeValue);
  }
}

13.1.5 节点树的动态性

DOM 节点树是 实时 的。当使用 querySelectorAll 返回的静态 NodeList 时(确切说是方法返回时才抓取快照),但 getElementsByClassNamechildNodes 等返回的是 动态 的集合。一旦文档结构发生变化,这些集合会自动反映改变。例如:

const list = document.getElementById('list');
const items = list.getElementsByTagName('li'); // 动态 HTMLCollection
console.log(items.length); // 3

const newLi = document.createElement('li');
list.appendChild(newLi);
console.log(items.length); // 4,自动更新

这种动态特性在遍历节点并同时修改结构时容易引发意外,需要注意循环控制和性能影响。

13.1.6 理解节点树的价值

把文档看作节点树,是我们用程序操控页面的根基。一切 UI 交互——无论是获取用户输入、修改样式、响应用户点击——本质上都是在遍历和修改这棵树的节点。掌握了节点类型和节点关系,就具备了程序化控制页面的基础能力。下一节将详细讲解如何有效地查找、创建和修改这些节点。