深入理解 DOM 结构与节点操作
·
在 Web 开发领域,文档对象模型(DOM) 是连接 HTML/XML 文档与 JavaScript 的桥梁。它通过树形结构将网页内容抽象为可编程的节点集合,使开发者能够动态修改页面内容、样式和交互逻辑。本文将系统解析 DOM 的核心概念、节点类型、操作方法及性能优化策略,帮助读者掌握前端开发的核心技能。
一、DOM 树:网页的逻辑骨架
1.1 DOM 的树形结构
DOM 将 HTML 文档解析为一棵节点树,每个节点代表文档的一部分(如元素、文本、属性等)。树的根节点是 document 对象,其结构如下:
mermaid
1graph TD
2 A[Document] --> B[html]
3 B --> C[head]
4 B --> D[body]
5 C --> E[title]
6 E --> F["DOM 示例"]
7 D --> G[div#container]
8 G --> H[h1]
9 H --> I["欢迎来到 DOM 世界"]
10 G --> J[p]
11 J --> K["这是一个示例文档"]
12
关键节点类型:
- 文档节点(Document):整个文档的根节点,通过
document对象访问。 - 元素节点(Element):HTML 标签(如
<div>、<p>),可通过nodeType === 1判断。 - 文本节点(Text):元素内的文本内容(如
<h1>中的文字),nodeType === 3。 - 属性节点(Attribute):元素的属性(如
class="container"),通过element.getAttributeNode()获取。 - 注释节点(Comment):HTML 注释(
<!-- -->),nodeType === 8。
1.2 节点关系与遍历
DOM 节点通过父子、兄弟关系构建层级结构,常用属性如下:
- 父子关系:
parentNode:获取父节点。childNodes:获取所有子节点(包括文本节点)。children:仅获取元素子节点(推荐使用)。firstChild/lastChild:获取第一个/最后一个子节点。
- 兄弟关系:
nextSibling/previousSibling:获取下一个/上一个兄弟节点。nextElementSibling/previousElementSibling:仅获取元素兄弟节点(忽略文本节点)。
示例:遍历 <body> 的所有子元素
javascript
1const body = document.body;
2for (let child of body.children) {
3 console.log(child.tagName); // 输出子元素的标签名
4}
5
二、DOM 节点操作:增删改查
2.1 获取节点
| 方法 | 描述 | 示例 |
|---|---|---|
document.getElementById(id) |
通过 ID 获取元素 | document.getElementById("header") |
document.querySelector(selector) |
通过 CSS 选择器获取第一个匹配元素 | document.querySelector(".btn") |
document.querySelectorAll(selector) |
获取所有匹配元素的集合 | document.querySelectorAll("li") |
element.getElementsByClassName(className) |
通过类名获取元素集合 | document.getElementsByClassName("item") |
2.2 创建节点
- 元素节点:
document.createElement(tagName)javascript1const newDiv = document.createElement("div"); 2newDiv.className = "box"; 3 - 文本节点:
document.createTextNode(text)javascript1const textNode = document.createTextNode("Hello DOM!"); 2 - 文档片段:
document.createDocumentFragment()(批量操作时减少重绘)javascript1const fragment = document.createDocumentFragment(); 2for (let i = 0; i < 100; i++) { 3 const li = document.createElement("li"); 4 li.textContent = `Item ${i}`; 5 fragment.appendChild(li); 6} 7document.getElementById("list").appendChild(fragment); 8
2.3 修改节点
- 添加子节点:
parentNode.appendChild(childNode):追加到末尾。parentNode.insertBefore(newNode, referenceNode):插入到指定节点前。
- 删除节点:
parentNode.removeChild(childNode)
javascript1const container = document.getElementById("container"); 2const oldChild = container.querySelector(".obsolete"); 3if (oldChild) container.removeChild(oldChild); 4 - 替换节点:
parentNode.replaceChild(newNode, oldNode)
javascript1const newParagraph = document.createElement("p"); 2newParagraph.textContent = "Updated content"; 3container.replaceChild(newParagraph, container.firstChild); 4
2.4 修改内容与属性
- 内容操作:
element.textContent:获取/设置纯文本(忽略 HTML 标签)。element.innerHTML:获取/设置包含 HTML 的字符串(需防范 XSS 攻击)。
- 属性操作:
element.setAttribute(name, value):设置属性。element.getAttribute(name):获取属性值。element.classList.add/remove/toggle(className):操作类名。
javascript1const btn = document.querySelector("button"); 2btn.setAttribute("data-action", "submit"); 3btn.classList.add("active"); 4
三、DOM 操作性能优化
3.1 减少重绘与重排
- 批量操作:使用
DocumentFragment或display: none隐藏元素后批量修改。 - 避免频繁查询:缓存 DOM 引用,避免在循环中重复查询。
javascript1// ❌ 反例:每次循环都查询 DOM 2for (let i = 0; i < 100; i++) { 3 document.getElementById("list").innerHTML += `<li>${i}</li>`; 4} 5 6// ✅ 优化:使用字符串拼接 + 一次性赋值 7let html = ""; 8for (let i = 0; i < 100; i++) { 9 html += `<li>${i}</li>`; 10} 11document.getElementById("list").innerHTML = html; 12
3.2 虚拟 DOM 与现代框架
React、Vue 等框架通过虚拟 DOM 抽象真实 DOM 操作,通过差异对比(Diffing)最小化重绘范围。例如:
javascript
1// React 示例:通过状态更新触发虚拟 DOM 比对
2function Counter() {
3 const [count, setCount] = useState(0);
4 return (
5 <div>
6 <p>Count: {count}</p>
7 <button onClick={() => setCount(count + 1)}>Increment</button>
8 </div>
9 );
10}
11
四、实战案例:动态表格生成
需求:根据用户输入动态生成表格,并支持行删除。
html
1<div>
2 <input type="text" id="rowData" placeholder="输入行数据(用逗号分隔)">
3 <button onclick="addRow()">添加行</button>
4</div>
5<table id="dataTable">
6 <thead>
7 <tr><th>序号</th><th>数据</th><th>操作</th></tr>
8 </thead>
9 <tbody></tbody>
10</table>
11
12<script>
13 const tableBody = document.querySelector("#dataTable tbody");
14 let rowCount = 0;
15
16 function addRow() {
17 const input = document.getElementById("rowData");
18 const data = input.value.split(",");
19 if (data.length < 1) return;
20
21 rowCount++;
22 const newRow = document.createElement("tr");
23 newRow.innerHTML = `
24 <td>${rowCount}</td>
25 <td>${data.join(", ")}</td>
26 <td><button onclick="deleteRow(this)">删除</button></td>
27 `;
28 tableBody.appendChild(newRow);
29 input.value = "";
30 }
31
32 function deleteRow(button) {
33 const row = button.closest("tr");
34 tableBody.removeChild(row);
35 // 重新编号
36 const rows = tableBody.querySelectorAll("tr");
37 rows.forEach((row, index) => {
38 row.cells[0].textContent = index + 1;
39 });
40 rowCount = rows.length;
41 }
42</script>
43
五、总结
DOM 是前端开发的核心技术,掌握其结构与操作方法能够显著提升开发效率。本文从 DOM 树的结构、节点操作方法、性能优化策略到实战案例,系统梳理了关键知识点。建议开发者:
- 优先使用
querySelector和classList:简化选择与类名操作。 - 谨慎操作
innerHTML:防范 XSS 攻击,优先使用textContent。 - 批量操作 DOM:减少重绘,提升性能。
- 学习虚拟 DOM 原理:理解 React/Vue 等框架的设计思想。
通过深入理解 DOM,开发者能够更高效地实现动态网页交互,为用户打造流畅的体验。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)