pin_drop当前位置:知识文库 ❯ 图文

JS集成:HTML DOM概述 - 详细教程与实战指南

教程简介

DOM(Document Object Model,文档对象模型)是HTML文档的编程接口,它将HTML文档表示为一个由节点组成的树形结构,使得JavaScript能够访问和操作文档的内容、结构和样式。理解DOM是前端开发的基础,所有页面交互、动态内容更新、用户界面响应都建立在DOM之上。本教程将全面讲解DOM的定义与结构、DOM树的概念、节点类型、DOM Level规范以及DOM与HTML的关系。


核心概念

什么是DOM

DOM是W3C(万维网联盟)制定的标准,它定义了访问和操作HTML/XML文档的标准方法。DOM将文档映射为一个由节点(Node)组成的树形结构,每个节点代表文档中的一部分(元素、文本、属性等),JavaScript通过DOM API来增删改查这些节点。

代码示例

DOM的核心作用:
1. 提供文档的结构化表示
2. 定义访问和操作文档的标准接口
3. 连接HTML文档与JavaScript脚本
4. 支持动态修改文档内容、结构和样式

DOM不是什么

  • DOM不是HTML:HTML是标记语言,DOM是HTML的编程接口

  • DOM不是JavaScript:DOM是独立于语言的API规范,JavaScript只是实现了DOM绑定

  • DOM不是源代码:DOM是浏览器解析HTML后生成的内部表示,可能与源代码不同


DOM树的概念

DOM树的结构

浏览器解析HTML文档后,会生成一个树形结构,称为DOM树。DOM树的根节点是document对象,每个HTML元素、文本内容、注释等都是树中的节点。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>DOM树示例</title>
</head>
<body>
  <h1>标题</h1>
  <p>段落文本</p>
</body>
</html>

上述HTML对应的DOM树结构:

代码示例

Document
└── html (Element)
    ├── head (Element)
    │   ├── meta (Element)
    │   │   └── charset="UTF-8" (Attribute)
    │   └── title (Element)
    │       └── "DOM树示例" (Text)
    └── body (Element)
        ├── h1 (Element)
        │   └── "标题" (Text)
        └── p (Element)
            └── "段落文本" (Text)

DOM树中的关系术语

代码示例

<div id="parent">
  <h2>标题</h2>
  <p>段落1</p>
  <p>段落2</p>
</div>

代码示例

关系术语:
- 父节点(Parent Node):div是h2和p的父节点
- 子节点(Child Node):h2和p是div的子节点
- 兄弟节点(Sibling Node):h2和p互为兄弟节点
- 祖先节点(Ancestor Node):div是h2中文本的祖先节点
- 后代节点(Descendant Node):h2中的文本是div的后代节点
- 第一个子节点(FirstChild):h2是div的第一个子元素
- 最后一个子节点(LastChild):第二个p是div的最后一个子元素
- 前一个兄弟(PreviousSibling):第一个p的前一个兄弟是h2
- 后一个兄弟(NextSibling):h2的后一个兄弟是第一个p

DOM树的遍历属性

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>DOM树遍历属性演示</title>
</head>
<body>
  <div id="container">
    <h2>标题</h2>
    <p>段落1</p>
    <p>段落2</p>
  </div>

  <script>
    var container = document.getElementById('container');

    // 父节点
    console.log('父节点:', container.parentNode);        // body
    console.log('父元素:', container.parentElement);     // body

    // 子节点(包含所有类型的节点,包括文本节点)
    console.log('子节点列表:', container.childNodes);     // NodeList [text, h2, text, p, text, p, text]
    console.log('子节点数量:', container.childNodes.length);

    // 子元素(只包含元素节点)
    console.log('子元素列表:', container.children);       // HTMLCollection [h2, p, p]
    console.log('子元素数量:', container.children.length);

    // 第一个/最后一个子节点
    console.log('第一个子节点:', container.firstChild);    // 可能是文本节点(换行/空格)
    console.log('第一个子元素:', container.firstElementChild);  // h2
    console.log('最后一个子节点:', container.lastChild);   // 可能是文本节点
    console.log('最后一个子元素:', container.lastElementChild); // p

    // 兄弟节点
    var h2 = container.firstElementChild;
    console.log('下一个兄弟节点:', h2.nextSibling);            // 可能是文本节点
    console.log('下一个兄弟元素:', h2.nextElementSibling);     // p
    console.log('上一个兄弟节点:', h2.previousSibling);        // 可能是文本节点
    console.log('上一个兄弟元素:', h2.previousElementSibling); // null(h2是第一个子元素)
  </script>
</body>
</html>

节点类型

DOM中的每个节点都有一个nodeType属性,用于标识节点的类型。DOM规范定义了12种节点类型,其中最常用的是元素节点、文本节点和属性节点。

节点类型一览表

nodeType值 常量 说明 示例
1 Node.ELEMENT_NODE 元素节点 <div>, <p>, <span>
2 Node.ATTRIBUTE_NODE 属性节点(已废弃) class="foo"
3 Node.TEXT_NODE 文本节点 元素中的文字内容
4 Node.CDATA_SECTION_NODE CDATA节点 <![CDATA[...]]>
8 Node.COMMENT_NODE 注释节点 <!-- 注释 -->
9 Node.DOCUMENT_NODE 文档节点 document
10 Node.DOCUMENT_TYPE_NODE 文档类型节点 <!DOCTYPE html>
11 Node.DOCUMENT_FRAGMENT_NODE 文档片段节点 document.createDocumentFragment()

常用节点类型详解

元素节点(Element Node, nodeType = 1):元素节点是DOM树中最常见的节点类型,对应HTML中的标签。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>元素节点示例</title>
</head>
<body>
  <div id="demo" class="container">
    <h1>标题</h1>
    <p>段落</p>
  </div>

  <script>
    var div = document.getElementById('demo');

    // 节点类型
    console.log('节点类型:', div.nodeType);          // 1
    console.log('节点名称:', div.nodeName);          // DIV(大写)
    console.log('节点值:', div.nodeValue);           // null(元素节点无nodeValue)

    // 元素特有属性
    console.log('标签名:', div.tagName);             // DIV
    console.log('ID:', div.id);                      // demo
    console.log('类名:', div.className);             // container
    console.log('是元素节点:', div.nodeType === Node.ELEMENT_NODE); // true

    // 判断是否为元素节点
    console.log('是元素:', div instanceof Element);   // true
    console.log('是HTMLElement:', div instanceof HTMLElement); // true
  </script>
</body>
</html>

文本节点(Text Node, nodeType = 3):文本节点包含元素中的文字内容,包括空格和换行。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文本节点示例</title>
</head>
<body>
  <p id="text-demo">Hello, World!</p>

  <script>
    var p = document.getElementById('text-demo');

    // p有一个子节点:文本节点
    var textNode = p.firstChild;

    console.log('节点类型:', textNode.nodeType);    // 3
    console.log('节点名称:', textNode.nodeName);    // #text
    console.log('节点值:', textNode.nodeValue);     // Hello, World!
    console.log('文本内容:', textNode.data);        // Hello, World!

    // 修改文本节点
    textNode.nodeValue = '文本已修改';
    // 或者
    textNode.data = '再次修改';

    // 文本节点的方法
    textNode.appendData(' 追加内容');
    textNode.insertData(0, '前缀 ');
    textNode.deleteData(0, 3);
    textNode.replaceData(0, 2, '替换');
  </script>
</body>
</html>

注释节点(Comment Node, nodeType = 8)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>注释节点示例</title>
</head>
<body>
  <div id="comment-demo">
    <!-- 这是一条注释 -->
    <p>段落内容</p>
    <!-- 这是另一条注释 -->
  </div>

  <script>
    var div = document.getElementById('comment-demo');
    var children = div.childNodes;

    for (var i = 0; i < children.length; i++) {
      var node = children[i];
      if (node.nodeType === Node.COMMENT_NODE) {
        console.log('发现注释节点:', node.nodeValue);
        // 输出:这是一条注释
        // 输出:这是另一条注释
      }
    }

    // 创建注释节点
    var comment = document.createComment('动态创建的注释');
    div.appendChild(comment);
  </script>
</body>
</html>

文档节点(Document Node, nodeType = 9)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文档节点示例</title>
</head>
<body>
  <script>
    // document就是文档节点
    console.log('节点类型:', document.nodeType);     // 9
    console.log('节点名称:', document.nodeName);     // #document
    console.log('节点值:', document.nodeValue);      // null

    // document的常用属性
    console.log('文档根元素:', document.documentElement);  // html
    console.log('head元素:', document.head);
    console.log('body元素:', document.body);
    console.log('文档标题:', document.title);
    console.log('文档URL:', document.URL);
    console.log('文档域名:', document.domain);
    console.log('引用来源:', document.referrer);
    console.log('最后修改时间:', document.lastModified);
    console.log('就绪状态:', document.readyState);
    console.log('所有表单:', document.forms);
    console.log('所有图片:', document.images);
    console.log('所有链接:', document.links);
  </script>
</body>
</html>

文档类型节点(DocumentType Node, nodeType = 10)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文档类型节点示例</title>
</head>
<body>
  <script>
    var doctype = document.doctype;

    if (doctype) {
      console.log('节点类型:', doctype.nodeType);    // 10
      console.log('节点名称:', doctype.nodeName);    // html
      console.log('公共标识符:', doctype.publicId);  // ""
      console.log('系统标识符:', doctype.systemId);  // ""
      console.log('内部子集:', doctype.internalSubset); // null
    }
  </script>
</body>
</html>

文档片段节点(DocumentFragment Node, nodeType = 11)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文档片段节点示例</title>
</head>
<body>
  <ul id="list"></ul>

  <script>
    // 创建文档片段
    var fragment = document.createDocumentFragment();

    console.log('节点类型:', fragment.nodeType);    // 11
    console.log('节点名称:', fragment.nodeName);    // #document-fragment

    // 在片段中添加多个元素(不会触发重排)
    for (var i = 1; i <= 5; i++) {
      var li = document.createElement('li');
      li.textContent = '项目 ' + i;
      fragment.appendChild(li);
    }

    // 一次性将片段插入DOM(只触发一次重排)
    document.getElementById('list').appendChild(fragment);
    console.log('列表项已添加');
  </script>
</body>
</html>

DOM Level介绍

DOM规范分为多个Level,每个Level在前一个的基础上增加了新的功能。

DOM Level 0

DOM Level 0并非W3C标准,而是指DOM标准化之前浏览器实现的早期DOM接口。主要包括:

  • document.images - 获取所有图片

  • document.forms - 获取所有表单

  • document.links - 获取所有链接

  • document.anchors - 获取所有锚点

  • element.onclick 等事件属性

代码示例

<script>
  // DOM Level 0 风格的代码
  document.getElementById('myForm').submit();
  document.images[0].src = 'new-image.png';
  document.forms[0].elements['username'].value;
</script>

DOM Level 1

DOM Level 1是W3C发布的第一个DOM标准(1998年),分为Core和HTML两个模块:

  • Core模块:定义了基本的DOM接口,适用于XML和HTML

  • HTML模块:定义了HTML特有的接口

代码示例

// DOM Level 1 Core
var element = document.createElement('div');
var text = document.createTextNode('Hello');
element.appendChild(text);

// DOM Level 1 HTML
var body = document.body;
var title = document.title;

DOM Level 2

DOM Level 2(2000年)在Level 1基础上增加了多个模块:

  • Core:扩展了Level 1 Core

  • Views:文档视图接口

  • Events:事件模型(addEventListener等)

  • Style:样式操作接口

  • Traversal and Range:遍历和范围接口

  • HTML:扩展了Level 1 HTML

代码示例

// DOM Level 2 Events
element.addEventListener('click', handler, false);

// DOM Level 2 Style
var style = window.getComputedStyle(element);
element.style.setProperty('color', 'red');

// DOM Level 2 Traversal
var walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter);
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT);

// DOM Level 2 Range
var range = document.createRange();
range.selectNodeContents(element);

DOM Level 3

DOM Level 3(2004年)进一步扩展:

  • Core:增加了对XML命名空间的支持

  • Load and Save:文档加载和保存

  • Validation:文档验证

  • XPath:XPath支持

  • Events:增加了键盘事件等

代码示例

// DOM Level 3
element.textContent = 'Hello';  // Level 3 引入

// XPath
var result = document.evaluate('//div[@class="item"]', document, null,
  XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var element = result.singleNodeValue;

// 键盘事件
element.addEventListener('keydown', function(e) {
  console.log('Key:', e.key, 'Code:', e.code);
});

DOM Level 4(DOM Living Standard)

DOM Level 4及之后,W3C转向了Living Standard(持续更新标准),不再按Level发布:

代码示例

// 现代DOM API
element.closest('.parent');           // 向上查找最近的匹配祖先
element.matches('.selector');         // 测试是否匹配选择器
element.classList.toggle('active');   // 切换类名
element.append(), element.prepend();  // 插入内容
element.before(), element.after();    // 兄弟插入
element.remove();                     // 移除元素
element.replaceWith(newElement);      // 替换元素
NodeList.prototype.forEach();         // NodeList支持forEach

DOM Level演进总结

Level 年份 主要新增功能
Level 0 1997前 基础文档访问(images, forms等)
Level 1 1998 核心DOM接口、HTML DOM接口
Level 2 2000 事件模型、样式操作、遍历/范围
Level 3 2004 textContent、XPath、键盘事件
Level 4+ 持续更新 matches、closest、classList、MutationObserver等

DOM与HTML的关系

HTML到DOM的转换过程

代码示例

HTML源代码 → 字节流解码 → 字符流 → 词法分析(Tokenizer)→ 令牌(Tokens)
→ 语法分析 → DOM节点 → DOM树

详细步骤:

  1. 字节流解码:将网络传输的字节流按字符编码解码为字符

  2. 输入流预处理:处理换行符等

  3. 词法分析:将字符流分解为令牌(Token)

  4. 树构建:根据令牌构建DOM树

HTML与DOM的差异

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>HTML与DOM的差异</title>
</head>
<body>
  <script>
    // 差异1:HTML中的错误会被浏览器自动修正
    // 源代码:<p>段落1<p>段落2
    // DOM中:<p>段落1</p><p>段落2</p>(自动闭合)

    // 差异2:HTML标签名不区分大小写,DOM中统一为大写
    // 源代码:<div id="test"></div>
    // DOM中:nodeName为"DIV"

    // 差异3:某些元素有隐式结构
    // 源代码:<table><tr><td>单元格</td></tr></table>
    // DOM中会自动添加<tbody>:<table><tbody><tr><td>单元格</td></tr></tbody></table>

    // 差异4:文本中的空白字符处理
    // 源代码中的换行和空格会生成文本节点
  </script>

  <!-- 演示:自动修正 -->
  <table id="demo-table">
    <tr><td>单元格</td></tr>
  </table>

  <script>
    var table = document.getElementById('demo-table');
    console.log('table的子元素:', table.children);
    // 输出包含自动添加的tbody

    // 差异5:属性名统一为小写
    // 源代码:<div CLASS="test">
    // DOM中:className为"test"
  </script>
</body>
</html>

DOM的动态性

DOM是动态的,JavaScript可以随时修改DOM树,浏览器会实时反映这些变化:

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>DOM的动态性</title>
</head>
<body>
  <div id="dynamic-demo">
    <p>初始内容</p>
  </div>

  <script>
    var demo = document.getElementById('dynamic-demo');

    // 1. 动态添加元素
    var newP = document.createElement('p');
    newP.textContent = '动态添加的段落';
    demo.appendChild(newP);

    // 2. 动态修改属性
    demo.setAttribute('class', 'highlight');

    // 3. 动态修改样式
    demo.style.color = 'blue';

    // 4. 动态删除元素
    var firstP = demo.querySelector('p');
    demo.removeChild(firstP);

    // 5. DOM变化会触发重排和重绘
    console.log('DOM已动态修改');
  </script>
</body>
</html>

代码示例

示例1:DOM树可视化工具

以下代码演示如何可视化当前页面的DOM树结构,包括节点类型、层级关系等信息。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DOM树可视化工具</title>
  <style>
    body { font-family: monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }
    .tree { padding-left: 20px; border-left: 1px solid #444; }
    .node { margin: 4px 0; padding: 4px 8px; border-radius: 3px; }
    .node-type-1 { background: #264f78; color: #9cdcfe; }   /* 元素节点 */
    .node-type-3 { background: #2d5a2d; color: #ce9178; }   /* 文本节点 */
    .node-type-8 { background: #4d4d2d; color: #6a9955; }   /* 注释节点 */
    .node-type-9 { background: #5a2d5a; color: #c586c0; }   /* 文档节点 */
    .node-type-10 { background: #5a4d2d; color: #dcdcaa; }  /* 文档类型节点 */
    .node-type-11 { background: #2d4d5a; color: #4ec9b0; }  /* 文档片段 */
    .toggle { cursor: pointer; user-select: none; }
    .toggle::before { content: '▼ '; }
    .toggle.collapsed::before { content: '▶ '; }
    .collapsed + .tree { display: none; }
    .info { font-size: 11px; color: #808080; margin-left: 8px; }
    h1 { color: #569cd6; }
  </style>
</head>
<body>
  <h1>DOM树可视化工具</h1>
  <div id="tree-container"></div>

  <script>
    function visualizeDOM(rootNode, container, maxDepth, currentDepth) {
      maxDepth = maxDepth || 10;
      currentDepth = currentDepth || 0;

      if (currentDepth > maxDepth) return;

      var nodeDiv = document.createElement('div');

      // 节点类型名称映射
      var typeNames = {
        1: 'Element', 3: 'Text', 8: 'Comment',
        9: 'Document', 10: 'DocumentType', 11: 'DocumentFragment'
      };

      var typeName = typeNames[rootNode.nodeType] || 'Unknown';
      var nodeClass = 'node node-type-' + rootNode.nodeType;

      // 构建节点显示文本
      var displayText = '';
      if (rootNode.nodeType === Node.ELEMENT_NODE) {
        displayText = '&lt;' + rootNode.nodeName.toLowerCase();
        if (rootNode.id) displayText += ' #' + rootNode.id;
        if (rootNode.className && typeof rootNode.className === 'string') {
          displayText += ' .' + rootNode.className.split(' ').join('.');
        }
        displayText += '&gt;';
      } else if (rootNode.nodeType === Node.TEXT_NODE) {
        var text = rootNode.nodeValue.trim();
        if (text.length > 50) text = text.substring(0, 50) + '...';
        if (text) displayText = '"' + text + '"';
        else displayText = '(空白文本)';
      } else if (rootNode.nodeType === Node.COMMENT_NODE) {
        displayText = '&lt;!-- ' + rootNode.nodeValue.trim() + ' --&gt;';
      } else {
        displayText = rootNode.nodeName;
      }

      var hasChildren = rootNode.childNodes && rootNode.childNodes.length > 0;

      nodeDiv.innerHTML =
        '<div class="' + nodeClass + (hasChildren ? ' toggle' : '') + '">' +
        displayText +
        '<span class="info">' + typeName + ' (nodeType: ' + rootNode.nodeType + ')</span>' +
        '</div>';

      if (hasChildren) {
        var childTree = document.createElement('div');
        childTree.className = 'tree';

        for (var i = 0; i < rootNode.childNodes.length; i++) {
          // 跳过script标签的子节点(避免递归执行)
          if (rootNode.childNodes[i].nodeName === 'SCRIPT') continue;
          visualizeDOM(rootNode.childNodes[i], childTree, maxDepth, currentDepth + 1);
        }

        nodeDiv.appendChild(childTree);

        // 点击折叠/展开
        var toggleEl = nodeDiv.querySelector('.toggle');
        toggleEl.addEventListener('click', function() {
          this.classList.toggle('collapsed');
        });
      }

      container.appendChild(nodeDiv);
    }

    // 可视化当前页面的DOM树
    visualizeDOM(document, document.getElementById('tree-container'), 8);
  </script>
</body>
</html>

示例2:节点类型检测工具

以下代码演示如何检测和分析DOM树中的节点类型,包括元素节点、文本节点、注释节点等。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节点类型检测工具</title>
  <style>
    body { font-family: -apple-system, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    .result { background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0; }
    .type-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; color: white; font-size: 12px; }
    .type-1 { background: #2196F3; }
    .type-3 { background: #4CAF50; }
    .type-8 { background: #FF9800; }
    .type-9 { background: #9C27B0; }
    .type-11 { background: #00BCD4; }
    table { width: 100%; border-collapse: collapse; margin: 10px 0; }
    th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #ddd; }
    th { background: #f0f0f0; }
  </style>
</head>
<body>
  <h1>节点类型检测工具</h1>

  <!-- 测试区域 -->
  <div id="test-area">
    <!-- 这是一个注释 -->
    <h2>标题文本</h2>
    <p>段落文本 <strong>加粗文本</strong></p>
    <ul>
      <li>列表项1</li>
      <li>列表项2</li>
    </ul>
  </div>

  <h2>节点分析结果</h2>
  <div id="analysis-result"></div>

  <script>
    function analyzeNodes(parent) {
      var result = [];
      var children = parent.childNodes;

      for (var i = 0; i < children.length; i++) {
        var node = children[i];
        var info = {
          index: i,
          nodeType: node.nodeType,
          typeName: getNodeTypeName(node.nodeType),
          nodeName: node.nodeName,
          nodeValue: node.nodeValue ? (node.nodeValue.length > 30 ?
            node.nodeValue.substring(0, 30) + '...' : node.nodeValue) : null,
          hasChildren: node.childNodes && node.childNodes.length > 0,
          childCount: node.childNodes ? node.childNodes.length : 0
        };
        result.push(info);

        // 递归分析子节点(跳过script)
        if (node.nodeName !== 'SCRIPT' && info.hasChildren) {
          info.children = analyzeNodes(node);
        }
      }

      return result;
    }

    function getNodeTypeName(type) {
      var names = {
        1: 'Element', 2: 'Attribute', 3: 'Text', 4: 'CDATA',
        5: 'EntityRef', 6: 'Entity', 7: 'ProcessingInstruction',
        8: 'Comment', 9: 'Document', 10: 'DocumentType',
        11: 'DocumentFragment', 12: 'Notation'
      };
      return names[type] || 'Unknown';
    }

    function renderAnalysis(nodes, depth) {
      depth = depth || 0;
      var html = '';
      var indent = '&nbsp;&nbsp;'.repeat(depth);

      nodes.forEach(function(node) {
        html += '<tr>';
        html += '<td>' + indent + node.index + '</td>';
        html += '<td><span class="type-badge type-' + node.nodeType + '">' +
          node.typeName + '</span></td>';
        html += '<td>' + node.nodeName + '</td>';
        html += '<td>' + (node.nodeValue || '-') + '</td>';
        html += '<td>' + node.childCount + '</td>';
        html += '</tr>';

        if (node.children) {
          html += renderAnalysis(node.children, depth + 1);
        }
      });

      return html;
    }

    // 执行分析
    var testArea = document.getElementById('test-area');
    var nodes = analyzeNodes(testArea);

    var resultHtml = '<table><thead><tr>' +
      '<th>索引</th><th>类型</th><th>名称</th><th>值</th><th>子节点数</th>' +
      '</tr></thead><tbody>';
    resultHtml += renderAnalysis(nodes);
    resultHtml += '</tbody></table>';

    document.getElementById('analysis-result').innerHTML = resultHtml;
  </script>
</body>
</html>

示例3:DOM遍历与操作综合示例

以下代码演示如何遍历DOM树、统计节点类型、执行DOM操作等综合功能。

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DOM遍历与操作综合示例</title>
  <style>
    body { font-family: -apple-system, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; }
    .card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 12px 0; }
    .card h3 { margin-top: 0; color: #333; }
    .btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 4px; }
    .btn-primary { background: #2196F3; color: white; }
    .btn-danger { background: #f44336; color: white; }
    .btn-success { background: #4CAF50; color: white; }
    .output { background: #f5f5f5; padding: 12px; border-radius: 4px; margin-top: 8px;
      font-family: monospace; font-size: 13px; white-space: pre-wrap; max-height: 200px; overflow-y: auto; }
  </style>
</head>
<body>
  <h1>DOM遍历与操作综合示例</h1>

  <!-- DOM结构演示区 -->
  <div class="card">
    <h3>DOM结构</h3>
    <div id="demo-tree">
      <header>
        <h2>网站标题</h2>
        <nav>
          <a href="#home">首页</a>
          <a href="#about">关于</a>
          <a href="#contact">联系</a>
        </nav>
      </header>
      <main>
        <article>
          <h3>文章标题</h3>
          <p>文章内容段落。</p>
        </article>
        <aside>
          <p>侧边栏内容</p>
        </aside>
      </main>
      <footer>
        <p>版权信息</p>
      </footer>
    </div>
  </div>

  <!-- 遍历操作区 -->
  <div class="card">
    <h3>DOM遍历操作</h3>
    <button class="btn btn-primary" onclick="traverseAllNodes()">遍历所有节点</button>
    <button class="btn btn-primary" onclick="traverseElements()">只遍历元素节点</button>
    <button class="btn btn-primary" onclick="traverseTextNodes()">只遍历文本节点</button>
    <button class="btn btn-success" onclick="countNodeTypes()">统计节点类型</button>
    <button class="btn btn-danger" onclick="clearOutput()">清除输出</button>
    <div id="traverse-output" class="output"></div>
  </div>

  <script>
    var demoTree = document.getElementById('demo-tree');
    var output = document.getElementById('traverse-output');

    function log(msg) {
      output.textContent += msg + '\n';
      output.scrollTop = output.scrollHeight;
    }

    function clearOutput() {
      output.textContent = '';
    }

    // 遍历所有节点
    function traverseAllNodes() {
      clearOutput();
      log('=== 遍历所有节点 ===');
      walkNodes(demoTree, 0);
    }

    function walkNodes(node, depth) {
      var indent = '  '.repeat(depth);
      var info = '';

      switch (node.nodeType) {
        case Node.ELEMENT_NODE:
          info = indent + '<' + node.nodeName.toLowerCase() + '>';
          break;
        case Node.TEXT_NODE:
          var text = node.nodeValue.trim();
          if (text) info = indent + '"' + text + '"';
          else info = indent + '(空白文本)';
          break;
        case Node.COMMENT_NODE:
          info = indent + '<!-- ' + node.nodeValue + ' -->';
          break;
        default:
          info = indent + '[' + node.nodeName + ']';
      }

      if (info) log(info);

      for (var i = 0; i < node.childNodes.length; i++) {
        walkNodes(node.childNodes[i], depth + 1);
      }
    }

    // 只遍历元素节点
    function traverseElements() {
      clearOutput();
      log('=== 只遍历元素节点 ===');
      walkElements(demoTree, 0);
    }

    function walkElements(element, depth) {
      var indent = '  '.repeat(depth);
      log(indent + '<' + element.nodeName.toLowerCase() + '>');

      var children = element.children;
      for (var i = 0; i < children.length; i++) {
        walkElements(children[i], depth + 1);
      }
    }

    // 只遍历文本节点
    function traverseTextNodes() {
      clearOutput();
      log('=== 只遍历文本节点 ===');
      walkTextNodes(demoTree);
    }

    function walkTextNodes(node) {
      if (node.nodeType === Node.TEXT_NODE) {
        var text = node.nodeValue.trim();
        if (text) {
          log('文本: "' + text + '" (父元素: ' + node.parentNode.nodeName + ')');
        }
      }

      for (var i = 0; i < node.childNodes.length; i++) {
        walkTextNodes(node.childNodes[i]);
      }
    }

    // 统计节点类型
    function countNodeTypes() {
      clearOutput();
      log('=== 统计节点类型 ===');

      var counts = {};
      countNodes(demoTree, counts);

      for (var type in counts) {
        var typeName = {1:'Element', 3:'Text', 8:'Comment', 9:'Document', 11:'Fragment'}[type] || 'Other';
        log(typeName + ' (type=' + type + '): ' + counts[type] + ' 个');
      }

      var total = 0;
      for (var t in counts) total += counts[t];
      log('总计: ' + total + ' 个节点');
    }

    function countNodes(node, counts) {
      var type = node.nodeType;
      counts[type] = (counts[type] || 0) + 1;

      for (var i = 0; i < node.childNodes.length; i++) {
        countNodes(node.childNodes[i], counts);
      }
    }
  </script>
</body>
</html>

浏览器兼容性

DOM API兼容性

特性 Chrome Firefox Safari Edge IE
DOM Level 1 Core 全部 全部 全部 全部 6+
DOM Level 2 Core 全部 全部 全部 全部 9+
DOM Level 2 Events 全部 全部 全部 全部 9+
DOM Level 2 Style 全部 全部 全部 全部 9+
textContent 全部 全部 全部 全部 9+
children 属性 全部 全部 全部 全部 9+
classList 全部 全部 全部 全部 10+
matches() 34+ 44+ 7+ 12+ 9+(msMatchesSelector)
closest() 41+ 40+ 6+ 15+ 不支持
MutationObserver 26+ 14+ 7+ 12+ 11+

注意事项与最佳实践

1. 理解文本节点的影响

代码示例

<!-- 注意:元素之间的空白会生成文本节点 -->
<div id="example">
  <p>段落1</p>
  <p>段落2</p>
</div>

<script>
  var div = document.getElementById('example');
  // div.childNodes 包含:text, p, text, p, text(5个节点)
  // div.children 包含:p, p(2个元素)

  // 推荐使用 children 而非 childNodes 遍历元素
  var elements = div.children;
  for (var i = 0; i < elements.length; i++) {
    console.log(elements[i].nodeName);
  }
</script>

2. 使用Element Traversal API

代码示例

// 旧方式:可能遇到文本节点
var firstChild = element.firstChild;       // 可能是文本节点
var nextSibling = element.nextSibling;      // 可能是文本节点

// 推荐方式:只遍历元素
var firstElement = element.firstElementChild;
var nextElement = element.nextElementSibling;
var lastElement = element.lastElementChild;
var prevElement = element.previousElementSibling;
var childElements = element.children;
var childElementCount = element.childElementCount;

3. 减少DOM操作以提升性能

代码示例

// 不推荐:频繁操作DOM
for (var i = 0; i < 1000; i++) {
  var li = document.createElement('li');
  li.textContent = 'Item ' + i;
  list.appendChild(li);  // 每次都触发重排
}

// 推荐:使用DocumentFragment
var fragment = document.createDocumentFragment();
for (var i = 0; i < 1000; i++) {
  var li = document.createElement('li');
  li.textContent = 'Item ' + i;
  fragment.appendChild(li);  // 不触发重排
}
list.appendChild(fragment);  // 只触发一次重排

4. 使用textContent替代innerHTML

代码示例

// 不推荐:有XSS风险
element.innerHTML = userInput;

// 推荐:安全地设置文本
element.textContent = userInput;

常见问题与解决方案

问题1:childNodes与children的区别导致遍历错误

症状: 使用childNodes遍历时遇到意外的文本节点。

解决方案:

代码示例

// 方案1:使用children只获取元素节点
var elements = parent.children;

// 方案2:遍历childNodes时检查nodeType
var children = parent.childNodes;
for (var i = 0; i < children.length; i++) {
  if (children[i].nodeType === Node.ELEMENT_NODE) {
    // 处理元素节点
  }
}

// 方案3:使用Element Traversal API
var firstElement = parent.firstElementChild;
while (firstElement) {
  // 处理元素
  firstElement = firstElement.nextElementSibling;
}

问题2:DOM操作导致页面卡顿

症状: 大量DOM操作导致页面响应缓慢。

解决方案:

代码示例

// 方案1:使用DocumentFragment
var fragment = document.createDocumentFragment();
// ... 批量操作
container.appendChild(fragment);

// 方案2:隐藏元素后操作
container.style.display = 'none';
// ... 大量操作
container.style.display = '';

// 方案3:使用requestAnimationFrame分批处理
function batchProcess(items, processFn, batchSize) {
  var index = 0;

  function processBatch() {
    var end = Math.min(index + batchSize, items.length);
    for (var i = index; i < end; i++) {
      processFn(items[i]);
    }
    index = end;
    if (index < items.length) {
      requestAnimationFrame(processBatch);
    }
  }

  processBatch();
}

问题3:IE8及以下不支持children属性

解决方案:

代码示例

// 兼容性封装
function getChildren(element) {
  if (element.children) {
    return element.children;
  }
  // 回退方案
  var result = [];
  var children = element.childNodes;
  for (var i = 0; i < children.length; i++) {
    if (children[i].nodeType === 1) {
      result.push(children[i]);
    }
  }
  return result;
}

总结

DOM是前端开发的核心基础,理解DOM的结构和操作方式对于构建交互式Web应用至关重要:

  1. DOM定义:DOM是HTML文档的编程接口,将文档表示为节点树

  2. DOM树:文档被解析为树形结构,节点之间有父子、兄弟等关系

  3. 节点类型:12种节点类型中,最常用的是元素节点(1)、文本节点(3)和注释节点(8)

  4. DOM Level:从Level 0到Living Standard,DOM规范不断演进,增加了更多功能

  5. DOM与HTML:DOM是HTML解析后的内部表示,可能与源代码有差异

  6. 性能优化:使用DocumentFragment、缓存DOM引用、减少重排重绘

  7. 安全实践:使用textContent替代innerHTML,防止XSS攻击

掌握DOM的基础知识,是深入学习DOM选择、内容修改、属性操作、样式控制和节点管理等高级主题的前提。

常见问题

DOM和HTML有什么区别?

HTML是标记语言,是源代码;DOM是HTML解析后生成的编程接口,是树形结构。DOM可以被JavaScript动态修改,而HTML源代码不会改变。

childNodes和children有什么区别?

childNodes返回所有子节点(包括文本节点、注释节点等),children只返回元素节点。推荐使用children遍历元素,避免文本节点干扰。

如何提高DOM操作的性能?

使用DocumentFragment批量操作DOM、缓存DOM引用、避免频繁触发重排重绘、使用textContent替代innerHTML、使用事件委托减少事件监听器数量。

什么是DocumentFragment?

DocumentFragment是轻量级的文档片段,不在DOM树中显示。使用它批量添加元素可以避免多次重排,提高性能。

为什么推荐使用textContent而不是innerHTML?

innerHTML会解析HTML字符串,存在XSS攻击风险;textContent只设置纯文本,更安全。性能上textContent也更快。

标签: DOM基础 DOM树结构 节点类型 DOM Level DOM操作 前端开发

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: JS集成:HTML defer与asyn - 详细教程与实战指南 下一篇: JS集成:HTML DOM选择元素 - 详细教程与实战指南

poll相关推荐