
文档对象模型(DOM)是HTML文档的编程接口,它以树形结构表示网页内容,允许开发者通过脚本(如JavaScript)动态访问和修改页面元素。以下从结构解析、节点类型、解析流程及优化等方面展开说明:
一、DOM的树形结构
在HTML文档中,DOM(文档对象模型)树是一种表示文档结构的方式。每个节点代表文档的一部分,如元素、属性或文本。下面是一个简单的HTML示例及其对应的DOM树节点结构图。
一个简单的dom树示意图
假设我们有如下HTML代码:
<!DOCTYPE html> <html> <head> <title>我的网页</title> </head> <body> <h1>欢迎来到我的网页</h1> <p>这是一个段落。</p> </body> </html>对应的DOM树节点结构可以示意如下:
Document └─ html ├─ head │ └─ title │ └─ #text: "我的网页" └─ body ├─ h1 │ └─ #text: "欢迎来到我的网页" └─ p └─ #text: "这是一个段落。"在这个结构中:
Document是整个文档的根节点。html节点是HTML文档的根元素。head和body都是html的子节点。title,h1, 和p分别是它们各自父节点下的子节点。#text表示文本节点,包含实际显示在页面上的文字内容。请注意,实际的DOM树可能会根据具体的HTML代码更加复杂,并且会包含更多类型的节点,比如注释节点、doctype声明等。上述示意图仅为简化版,用于帮助理解DOM树的基本结构。
一个更加复杂的dom树示意图
这里有一个更加复杂的HTML示例及其对应的DOM树节点结构图。这个示例将包含更多种类的元素和结构,例如列表、链接、图片等。
假设我们有如下HTML代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>我的复杂网页</title> </head> <body> <header> <h1>欢迎来到我的复杂网页</h1> <nav> <ul> <li><a href="#home">首页</a></li> <li><a href="#about">关于我们</a></li> <li><a href="#services">服务</a></li> <li><a href="#contact">联系我们</a></li> </ul> </nav> </header> <main> <section id="home"> <h2>首页</h2> <p>这是首页的内容。</p> </section> <section id="about"> <h2>关于我们</h2> <p>这是关于我们的内容。</p> </section> <section id="services"> <h2>我们的服务</h2> <p>这是服务部分的内容。</p> <img src="service.jpg" alt="服务图片"> </section> <section id="contact"> <h2>联系我们</h2> <form action="/submit-contact" method="post"> <label for="email">邮箱:</label> <input type="email" id="email" name="email"><br> <button type="submit">提交</button> </form> </section> </main> <footer> <p>版权所有 © 2025 我的公司</p> </footer> </body> </html>对应的DOM树节点结构可以示意如下:
Document └─ html[lang="zh-CN"] ├─ head │ ├─ meta[charset="UTF-8"] │ └─ title │ └─ #text: "我的复杂网页" └─ body ├─ header │ ├─ h1 │ │ └─ #text: "欢迎来到我的复杂网页" │ └─ nav │ └─ ul │ ├─ li │ │ └─ a[href="#home"] │ │ └─ #text: "首页" │ ├─ li │ │ └─ a[href="#about"] │ │ └─ #text: "关于我们" │ ├─ li │ │ └─ a[href="#services"] │ │ └─ #text: "服务" │ └─ li │ └─ a[href="#contact"] │ └─ #text: "联系我们" ├─ main │ ├─ section[id="home"] │ │ ├─ h2 │ │ │ └─ #text: "首页" │ │ └─ p │ │ └─ #text: "这是首页的内容。" │ ├─ section[id="about"] │ │ ├─ h2 │ │ │ └─ #text: "关于我们" │ │ └─ p │ │ └─ #text: "这是关于我们的内容。" │ ├─ section[id="services"] │ │ ├─ h2 │ │ │ └─ #text: "我们的服务" │ │ ├─ p │ │ │ └─ #text: "这是服务部分的内容。" │ │ └─ img[src="service.jpg", alt="服务图片"] │ └─ section[id="contact"] │ ├─ h2 │ │ └─ #text: "联系我们" │ └─ form[action="/submit-contact", method="post"] │ ├─ label[for="email"] │ │ └─ #text: "邮箱:" │ ├─ input[type="email", id="email", name="email"] │ └─ button[type="submit"] │ └─ #text: "提交" └─ footer └─ p └─ #text: "版权所有 © 2025 我的公司"在这个更复杂的DOM树中,我们可以看到:
- HTML文档的基本结构包括
<head>和<body>。<header>包含了页面的标题和导航栏。<main>标签内部是主要内容区域,分为几个不同的<section>,每个都有自己的标题和内容。<footer>用于放置页脚信息。这种结构展示了如何通过嵌套各种HTML标签来构建一个复杂的网页,并且每一个标签都可以看作是DOM树的一个节点。
二、DOM 节点的定义
DOM 树的节点类型由 Node 接口定义。那么,node是什么?
关于node
在网页开发的上下文中,特别是在使用如HTML、XML这些标记语言时,”node”(节点)是构成文档对象模型(DOM, Document Object Model)的基本组成部分。DOM是一种编程接口,用于HTML和XML文档。它表示文档的形式化的方式,允许程序和脚本动态地访问和更新文档的内容、结构及样式。
Node接口是所有节点类型的基础接口,定义了可用于所有类型的节点的方法和属性。
nodeType 是 DOM 节点的一个属性,用于表示节点的类型。每个 DOM 节点都属于特定的类型(如元素、文本、注释等),nodeType 返回一个数字常量,帮助开发者快速判断当前节点的类型。
以下是主要的 nodeType 值及其含义:
| 值 | 常量名 | 说明 |
|---|---|---|
1 | Node.ELEMENT_NODE | 元素节点(例如 <div>、<p> 等 HTML 标签)。 |
2 | Node.ATTRIBUTE_NODE | 属性节点(例如 <img src="..."> 中的 src 属性,极少直接操作)。 |
3 | Node.TEXT_NODE | 文本节点(元素内的文本内容,包括空格和换行)。 |
8 | Node.COMMENT_NODE | 注释节点(HTML 中的 <!-- 注释内容 -->)。 |
9 | Node.DOCUMENT_NODE | 文档节点(document 对象,代表整个 HTML 文档的根)。 |
10 | Node.DOCUMENT_TYPE_NODE | 文档类型声明(例如 <!DOCTYPE html>)。 |
11 | Node.DOCUMENT_FRAGMENT_NODE | 文档片段节点(轻量化的临时容器,用于高效批量操作 DOM)。 |
JavaScript访问
nodeType的作用
- 判断节点类型
通过nodeType可以快速区分不同类型的节点,例如:const node = document.querySelector("div"); if (node.nodeType === Node.ELEMENT_NODE) { console.log("这是一个元素节点"); } else if (node.nodeType === Node.TEXT_NODE) { console.log("这是一个文本节点"); }
- 过滤无关节点
在处理子节点时,可能需要跳过文本节点或注释:const parent = document.getElementById("container"); for (const child of parent.childNodes) { if (child.nodeType === Node.ELEMENT_NODE) { // 只处理元素节点 console.log("元素标签名:", child.tagName); } }
注意事项
兼容性
Node.ELEMENT_NODE等常量是Node接口的静态属性,现代浏览器均支持,但在旧代码中可能直接使用数字(如1)判断。代码示例:
// 兼容性写法(推荐使用常量) if (node.nodeType === 1) { /* 元素节点 */ }属性节点的特殊性
属性节点(
nodeType === 2)虽然存在,但通常不通过 DOM 树的子节点访问,而是通过元素的attributes属性:const img = document.querySelector("img"); const srcAttr = img.attributes.getNamedItem("src"); console.log(srcAttr.nodeType); // 2空白文本节点
HTML 中的换行或缩进会被解析为文本节点(
nodeType === 3),可能包含空字符串:<div> <p>Hello</p> </div>
<div>和<p>之间的换行符会被视为一个文本节点。
DOM 树常见节点类型
DOM 树常见节点类型包括:
文档节点(Document)
- 表示整个 HTML 文档的根节点,
document对象。 nodeType值为9。
元素节点(Element)
- 对应 HTML 标签(如
<div>、<p>)。 nodeType值为1,可通过tagName获取标签名。
属性节点(Attr)
- 表示元素的属性(如
<img src="image.jpg">中的src)。 nodeType值为2,属于元素节点的子节点,但一般不直接操作。
文本节点(Text)
- 包含元素的文本内容(如
<p>Hello</p>中的Hello)。 nodeType值为3,没有子节点。
注释节点(Comment)
- 对应 HTML 注释(
<!-- comment -->)。 nodeType值为8。
文档片段节点(DocumentFragment)
- 轻量级的文档片段,用于高效批量操作 DOM。
nodeType值为11。
三、DOM 树的解析流程
浏览器接收到HTML文档后,逐行解析并生成Token,随后构建DOM树。若遇到<script>标签,会暂停解析并执行脚本(同步加载),可能导致页面渲染延迟。
浏览器解析 HTML 并构建 DOM 树的步骤如下:
词法分析(Tokenization)
- 将 HTML 字符串分解为标记(tokens),如开始标签
<div>、结束标签</div>、属性、文本等。
tokens是什么?
在处理HTML文档时,将HTML字符串分解为标记(tokens)是解析过程中的一个重要步骤。Tokens可以理解为构成HTML文档的基本单元,这些基本单元能够帮助解析器理解和构建文档的结构。
具体来说,当我们将HTML字符串分解为tokens时,通常会得到以下几种类型的tokens:
- 标签开始(Start Tag):比如
<div>,表示一个元素的开始。- 标签结束(End Tag):例如
</div>,表示之前开始的元素在此处结束。- 自闭合标签(Self-closing Tag):如
<img src="..." />,这种标签不需要对应的结束标签。- 属性(Attributes):出现在开始标签内,提供有关元素的额外信息,例如
<a href="http://example.com">中的href="http://example.com"。- 文本(Text):元素之间的实际内容,例如
<p>这里是段落文本。</p>中的“这里是段落文本。”- 注释(Comment):以
<!--开头,以-->结尾的内容,不会在浏览器中显示。通过将HTML文档分解成这些基础的tokens,解析器能够更高效地处理和操作文档。这个过程是实现动态网页内容更新、SEO优化以及各种前端框架功能的基础。例如,在React中,JSX语法最终会被转换成相应的HTML tags tokens以便渲染到页面上。此外,理解这一过程有助于开发者更好地掌握前端技术,提升网页开发技能。
构建节点(Node Creation)
- 根据标记创建对应的 DOM 节点。例如,遇到
<div>会创建元素节点,遇到文本内容会创建文本节点。
树形结构生成
- 通过栈结构维护节点层级关系:
- 遇到开始标签时,创建元素节点,并作为当前节点的子节点。
- 遇到结束标签时,弹出栈顶元素,回到父级上下文。
- 例如,解析以下 HTML:
<html>
<body>
<h1>Title</h1>
<p>Paragraph</p>
</body>
</html>生成的 DOM 树结构为:
Document
└── html
└── body
├── h1
│ └── "Title" (文本节点)
└── p
└── "Paragraph" (文本节点)容错处理
- 浏览器会自动修复不规范的 HTML(如未闭合的标签),确保生成合法的 DOM 树。
构建完成与后续流程
DOM树构建完成后,浏览器会触发DOMContentLoaded事件,随后结合CSSOM(CSS对象模型)生成渲染树(Render Tree),进入布局(Layout)和绘制(Paint)阶段。
四、JavaScript访问
节点关系与遍历
每个节点通过以下属性关联其他节点:
parentNode: 父节点。childNodes: 子节点列表(类数组对象)。firstChild/lastChild: 第一个/最后一个子节点。previousSibling/nextSibling: 前一个/后一个兄弟节点。
示例:遍历子节点
const element = document.querySelector('div');
for (const child of element.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) {
console.log('元素节点:', child.tagName);
} else if (child.nodeType === Node.TEXT_NODE) {
console.log('文本内容:', child.textContent);
}
}关键注意事项
- 空白文本节点
若 HTML 中存在换行或缩进,浏览器可能生成空的文本节点(可通过nodeType过滤)。 - 脚本阻塞解析
遇到<script>标签时,浏览器会暂停解析并执行脚本(除非标记为async或defer)。 - 动态修改 DOM
通过 JavaScript 动态修改 DOM(如appendChild()、innerHTML)会触发浏览器的重解析(reflow/repaint)。 - 优化策略
- 将
<script>标签置于<body>末尾,或使用async/defer属性实现异步加载。 - 使用
document.createElement动态添加元素,减少初始解析负担。
DOM操作与性能优化
- 常见操作
- 通过
getElementById、querySelector等方法获取节点。 - 使用
appendChild、removeChild增删节点。 - 修改节点属性或样式(如
element.style.color)。
- 通过
- 性能影响
DOM操作可能触发重排(Reflow)和重绘(Repaint),频繁操作会降低性能。优化方法包括:
- 使用
DocumentFragment批量操作节点。 - 通过
classList一次性修改多个样式,减少重排次数。
DOM与事件机制
DOM树的事件传播遵循捕获阶段 → 目标阶段 → 冒泡阶段。可通addEventListener绑定事件,并利用事件委托(Event Delegation)优化性能,例如将事件监听器绑定到父节点而非多个子节点。

