在 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)
    
      

    javascript

    1const newDiv = document.createElement("div");
    2newDiv.className = "box";
    3
  • 文本节点document.createTextNode(text)
    
      

    javascript

    1const textNode = document.createTextNode("Hello DOM!");
    2
  • 文档片段document.createDocumentFragment()(批量操作时减少重绘)
    
      

    javascript

    1const 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)
    
      

    javascript

    1const container = document.getElementById("container");
    2const oldChild = container.querySelector(".obsolete");
    3if (oldChild) container.removeChild(oldChild);
    4
  • 替换节点
    • parentNode.replaceChild(newNode, oldNode)
    
      

    javascript

    1const 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):操作类名。
    
      

    javascript

    1const btn = document.querySelector("button");
    2btn.setAttribute("data-action", "submit");
    3btn.classList.add("active");
    4

三、DOM 操作性能优化

3.1 减少重绘与重排

  • 批量操作:使用 DocumentFragment 或 display: none 隐藏元素后批量修改。
  • 避免频繁查询:缓存 DOM 引用,避免在循环中重复查询。
    
      

    javascript

    1// ❌ 反例:每次循环都查询 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 树的结构、节点操作方法、性能优化策略到实战案例,系统梳理了关键知识点。建议开发者:

  1. 优先使用 querySelector 和 classList:简化选择与类名操作。
  2. 谨慎操作 innerHTML:防范 XSS 攻击,优先使用 textContent
  3. 批量操作 DOM:减少重绘,提升性能。
  4. 学习虚拟 DOM 原理:理解 React/Vue 等框架的设计思想。

通过深入理解 DOM,开发者能够更高效地实现动态网页交互,为用户打造流畅的体验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐