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的后一个兄弟是第一个pDOM树的遍历属性
代码示例
<!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种节点类型,其中最常用的是元素节点、文本节点和属性节点。
节点类型一览表
常用节点类型详解
元素节点(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支持forEachDOM Level演进总结
DOM与HTML的关系
HTML到DOM的转换过程
代码示例
HTML源代码 → 字节流解码 → 字符流 → 词法分析(Tokenizer)→ 令牌(Tokens)
→ 语法分析 → DOM节点 → DOM树详细步骤:
-
字节流解码:将网络传输的字节流按字符编码解码为字符
-
输入流预处理:处理换行符等
-
词法分析:将字符流分解为令牌(Token)
-
树构建:根据令牌构建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 = '<' + rootNode.nodeName.toLowerCase();
if (rootNode.id) displayText += ' #' + rootNode.id;
if (rootNode.className && typeof rootNode.className === 'string') {
displayText += ' .' + rootNode.className.split(' ').join('.');
}
displayText += '>';
} 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 = '<!-- ' + rootNode.nodeValue.trim() + ' -->';
} 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 = ' '.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兼容性
注意事项与最佳实践
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应用至关重要:
-
DOM定义:DOM是HTML文档的编程接口,将文档表示为节点树
-
DOM树:文档被解析为树形结构,节点之间有父子、兄弟等关系
-
节点类型:12种节点类型中,最常用的是元素节点(1)、文本节点(3)和注释节点(8)
-
DOM Level:从Level 0到Living Standard,DOM规范不断演进,增加了更多功能
-
DOM与HTML:DOM是HTML解析后的内部表示,可能与源代码有差异
-
性能优化:使用DocumentFragment、缓存DOM引用、减少重排重绘
-
安全实践:使用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也更快。
本文涉及AI创作
内容由AI创作,请仔细甄别