Web API基础-DOM操作完全指南
# Web API基础-DOM操作完全指南
DOM(Document Object Model,文档对象模型)是Web开发的核心基础。浏览器将HTML文档解析为DOM树,JavaScript通过DOM API可以动态地访问、修改文档的结构、样式和内容。本文将系统地介绍DOM操作的方方面面。
# 一、DOM树结构与节点类型
# 1.1 DOM树结构
HTML文档被解析为树形结构:
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
</head>
<body>
<h1 id="title">欢迎</h1>
<p class="intro">这是一段介绍文字。</p>
</body>
</html>
对应的DOM树:
Document
└── html
├── head
│ └── title
│ └── "示例页面"
└── body
├── h1#title
│ └── "欢迎"
└── p.intro
└── "这是一段介绍文字。"
# 1.2 节点类型
DOM中的每个元素都是一个节点(Node),常见节点类型:
// 1. Element(元素节点)- nodeType: 1
const div = document.querySelector('div');
console.log(div.nodeType); // 1
console.log(div.nodeName); // "DIV"
// 2. Text(文本节点)- nodeType: 3
const text = div.firstChild;
console.log(text.nodeType); // 3
console.log(text.nodeName); // "#text"
// 3. Comment(注释节点)- nodeType: 8
// <!-- 这是注释 -->
const comment = document.createComment('这是注释');
console.log(comment.nodeType); // 8
// 4. Document(文档节点)- nodeType: 9
console.log(document.nodeType); // 9
console.log(document.nodeName); // "#document"
// 5. DocumentFragment(文档片段)- nodeType: 11
const fragment = document.createDocumentFragment();
console.log(fragment.nodeType); // 11
# 1.3 节点关系
const element = document.getElementById('container');
// 父节点
element.parentNode;
element.parentElement; // 只返回元素节点
// 子节点
element.childNodes; // 包含所有类型节点(含文本、注释)
element.children; // 只包含元素节点(推荐)
element.firstChild; // 第一个子节点
element.lastChild; // 最后一个子节点
element.firstElementChild; // 第一个子元素
element.lastElementChild; // 最后一个子元素
// 兄弟节点
element.previousSibling; // 前一个兄弟节点
element.nextSibling; // 后一个兄弟节点
element.previousElementSibling; // 前一个兄弟元素
element.nextElementSibling; // 后一个兄弟元素
# 二、选择DOM元素
# 2.1 传统选择方法
// 通过ID选择(返回单个元素)
const title = document.getElementById('title');
// 通过类名选择(返回HTMLCollection)
const items = document.getElementsByClassName('item');
// 通过标签名选择(返回HTMLCollection)
const paragraphs = document.getElementsByTagName('p');
// 通过name属性选择(常用于表单)
const inputs = document.getElementsByName('username');
# 2.2 现代选择器(推荐)
// querySelector(返回第一个匹配元素)
const title = document.querySelector('#title');
const firstItem = document.querySelector('.item');
const firstDiv = document.querySelector('div');
// querySelectorAll(返回所有匹配元素,NodeList)
const allItems = document.querySelectorAll('.item');
const allDivs = document.querySelectorAll('div');
// 支持复杂CSS选择器
const activeLink = document.querySelector('nav a.active');
const externalLinks = document.querySelectorAll('a[href^="http"]');
const firstParagraph = document.querySelector('article > p:first-child');
const evenItems = document.querySelectorAll('li:nth-child(even)');
# 2.3 HTMLCollection vs NodeList
// HTMLCollection(动态集合)
const divs1 = document.getElementsByTagName('div');
console.log(divs1.length); // 假设为 3
// 新增元素后,HTMLCollection自动更新
document.body.appendChild(document.createElement('div'));
console.log(divs1.length); // 变为 4
// NodeList(querySelectorAll返回静态集合)
const divs2 = document.querySelectorAll('div');
console.log(divs2.length); // 假设为 4
// 新增元素后,NodeList不会自动更新
document.body.appendChild(document.createElement('div'));
console.log(divs2.length); // 仍为 4
// NodeList转数组
const divsArray = Array.from(divs2);
const divsArray2 = [...divs2];
# 2.4 查找最近的祖先元素
const element = document.querySelector('.child');
// 查找最近的匹配选择器的祖先元素
const container = element.closest('.container');
const form = element.closest('form');
// 检查元素是否匹配选择器
if (element.matches('.active')) {
console.log('元素包含active类');
}
# 三、遍历DOM树
# 3.1 父子遍历
const parent = document.getElementById('parent');
// 遍历所有子元素
for (const child of parent.children) {
console.log(child.tagName);
}
// 递归遍历所有后代元素
function traverseDOM(element, callback) {
callback(element);
for (const child of element.children) {
traverseDOM(child, callback);
}
}
traverseDOM(document.body, (el) => {
console.log(el.tagName);
});
# 3.2 兄弟遍历
const element = document.querySelector('.current');
// 遍历所有后续兄弟元素
let sibling = element.nextElementSibling;
while (sibling) {
console.log(sibling);
sibling = sibling.nextElementSibling;
}
// 遍历所有前置兄弟元素
let prevSibling = element.previousElementSibling;
while (prevSibling) {
console.log(prevSibling);
prevSibling = prevSibling.previousElementSibling;
}
# 3.3 TreeWalker
TreeWalker提供更灵活的DOM遍历方式:
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT, // 只显示元素节点
{
acceptNode(node) {
// 只接受class包含'item'的元素
return node.classList.contains('item')
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
}
}
);
let node;
while (node = walker.nextNode()) {
console.log(node);
}
# 四、读取和修改元素内容
# 4.1 innerHTML vs textContent vs innerText
const div = document.querySelector('div');
// innerHTML:HTML字符串
div.innerHTML = '<strong>加粗文字</strong>';
console.log(div.innerHTML); // "<strong>加粗文字</strong>"
// textContent:纯文本(推荐)
div.textContent = '<strong>加粗文字</strong>';
console.log(div.textContent); // "<strong>加粗文字</strong>"(显示为文本)
// innerText:考虑样式的文本(会触发重排,性能较差)
div.innerText = '文本内容';
区别对比:
<div id="test" style="display: none;">
Hello <span>World</span>
</div>
<script>
const div = document.getElementById('test');
console.log(div.innerHTML); // "Hello <span>World</span>"
console.log(div.textContent); // "Hello World"
console.log(div.innerText); // ""(因为元素被隐藏)
</script>
# 4.2 outerHTML
const div = document.querySelector('div');
// outerHTML:包含元素本身
console.log(div.outerHTML); // "<div>内容</div>"
// 替换整个元素
div.outerHTML = '<p>新段落</p>'; // div被p替换
# 4.3 安全地插入HTML
// ✗ 避免:直接使用innerHTML(XSS风险)
const userInput = '<img src=x onerror="alert(1)">';
div.innerHTML = userInput; // 危险!
// ✓ 推荐:使用textContent
div.textContent = userInput; // 安全,作为文本显示
// ✓ 推荐:使用DOMPurify库清理HTML
// div.innerHTML = DOMPurify.sanitize(userInput);
# 五、属性操作
# 5.1 HTML属性操作
const link = document.querySelector('a');
// getAttribute / setAttribute
link.getAttribute('href'); // 获取属性
link.setAttribute('href', '/new'); // 设置属性
link.hasAttribute('target'); // 检查属性是否存在
link.removeAttribute('target'); // 删除属性
// 直接属性访问(推荐)
link.href = 'https://example.com';
link.target = '_blank';
console.log(link.href);
# 5.2 data-* 自定义属性
<div id="user"
data-id="123"
data-name="张三"
data-role="admin"
data-created-at="2024-01-15">
</div>
<script>
const user = document.getElementById('user');
// dataset API(推荐)
console.log(user.dataset.id); // "123"
console.log(user.dataset.name); // "张三"
console.log(user.dataset.role); // "admin"
console.log(user.dataset.createdAt); // "2024-01-15"(驼峰转换)
// 设置data属性
user.dataset.status = 'active';
user.dataset.lastLogin = '2024-01-20';
// 删除data属性
delete user.dataset.role;
// getAttribute方式
console.log(user.getAttribute('data-id')); // "123"
</script>
# 5.3 布尔属性
const checkbox = document.querySelector('input[type="checkbox"]');
// 布尔属性(checked、disabled、selected等)
checkbox.checked = true;
checkbox.disabled = false;
// 通过setAttribute设置布尔属性
checkbox.setAttribute('checked', ''); // 存在即为true
checkbox.removeAttribute('checked'); // 删除即为false
# 六、类名操作
# 6.1 className
const div = document.querySelector('div');
// className(字符串)
div.className = 'btn btn-primary active';
console.log(div.className); // "btn btn-primary active"
// 添加类(需要手动处理)
div.className += ' new-class';
# 6.2 classList(推荐)
const div = document.querySelector('div');
// add:添加类
div.classList.add('active');
div.classList.add('btn', 'btn-primary'); // 可添加多个
// remove:删除类
div.classList.remove('active');
div.classList.remove('btn', 'btn-primary');
// toggle:切换类
div.classList.toggle('active'); // 有则删,无则加
div.classList.toggle('active', true); // 强制添加
div.classList.toggle('active', false); // 强制删除
// contains:检查类是否存在
if (div.classList.contains('active')) {
console.log('包含active类');
}
// replace:替换类
div.classList.replace('old-class', 'new-class');
// 遍历类名
for (const className of div.classList) {
console.log(className);
}
# 七、样式操作
# 7.1 内联样式(style)
const div = document.querySelector('div');
// 设置样式
div.style.color = 'red';
div.style.backgroundColor = 'blue'; // CSS属性名转驼峰
div.style.fontSize = '16px';
// 读取内联样式
console.log(div.style.color); // "red"
// CSS属性名
div.style['background-color'] = 'blue';
// cssText:批量设置
div.style.cssText = 'color: red; background: blue; font-size: 16px;';
// 删除样式
div.style.color = '';
div.style.removeProperty('background-color');
# 7.2 计算样式(getComputedStyle)
const div = document.querySelector('div');
// 获取计算后的样式(包括CSS文件中的样式)
const styles = window.getComputedStyle(div);
console.log(styles.color); // 实际渲染的颜色
console.log(styles.fontSize); // 实际字体大小
console.log(styles.display); // 实际display值
console.log(styles.width); // 实际宽度(像素值)
// 获取伪元素样式
const beforeStyles = window.getComputedStyle(div, '::before');
console.log(beforeStyles.content);
# 7.3 样式最佳实践
// ✗ 避免:频繁操作style
for (let i = 0; i < 100; i++) {
div.style.width = i + 'px'; // 触发多次重排
}
// ✓ 推荐:通过classList切换预定义样式
div.classList.add('expanded');
// ✓ 推荐:批量修改使用cssText
div.style.cssText = 'width: 100px; height: 100px; background: red;';
# 八、创建和插入元素
# 8.1 创建元素
// 创建元素
const div = document.createElement('div');
div.className = 'container';
div.textContent = '内容';
// 创建文本节点
const text = document.createTextNode('文本内容');
// 创建注释
const comment = document.createComment('这是注释');
// 创建文档片段(性能优化)
const fragment = document.createDocumentFragment();
# 8.2 插入元素
const parent = document.getElementById('parent');
const newElement = document.createElement('div');
// appendChild:添加到末尾
parent.appendChild(newElement);
// insertBefore:插入到指定元素前
const reference = parent.firstElementChild;
parent.insertBefore(newElement, reference);
// append:现代方法,可插入多个节点/文本
parent.append(newElement, '文本', anotherElement);
// prepend:插入到开头
parent.prepend(newElement);
// before:插入到元素前
reference.before(newElement);
// after:插入到元素后
reference.after(newElement);
# 8.3 DocumentFragment优化
// ✗ 避免:多次插入触发重排
const ul = document.querySelector('ul');
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
ul.appendChild(li); // 触发100次重排
}
// ✓ 推荐:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment); // 只触发1次重排
# 8.4 克隆元素
const original = document.querySelector('.original');
// 浅克隆(不包含子元素)
const shallowClone = original.cloneNode(false);
// 深克隆(包含所有子元素)
const deepClone = original.cloneNode(true);
// 注意:事件监听器不会被克隆
# 九、删除和替换元素
# 9.1 删除元素
const element = document.querySelector('.to-remove');
// remove:删除自己(现代方法)
element.remove();
// removeChild:通过父元素删除
const parent = element.parentNode;
parent.removeChild(element);
// 删除所有子元素
const container = document.querySelector('.container');
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// 现代方法:replaceChildren
container.replaceChildren(); // 清空所有子元素
# 9.2 替换元素
const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = '新元素';
// replaceWith:替换自己
oldElement.replaceWith(newElement);
// replaceChild:通过父元素替换
const parent = oldElement.parentNode;
parent.replaceChild(newElement, oldElement);
// replaceChildren:替换所有子元素
const container = document.querySelector('.container');
container.replaceChildren(newElement); // 清空并添加新元素
# 十、元素尺寸和位置
# 10.1 元素尺寸
const element = document.querySelector('.box');
// offsetWidth/offsetHeight:包含边框和滚动条
console.log(element.offsetWidth); // width + padding + border
console.log(element.offsetHeight); // height + padding + border
// clientWidth/clientHeight:不含边框和滚动条
console.log(element.clientWidth); // width + padding
console.log(element.clientHeight); // height + padding
// scrollWidth/scrollHeight:内容实际宽高(含溢出部分)
console.log(element.scrollWidth);
console.log(element.scrollHeight);
// getBoundingClientRect:相对视口的位置和尺寸
const rect = element.getBoundingClientRect();
console.log(rect.top); // 距离视口顶部
console.log(rect.left); // 距离视口左侧
console.log(rect.width); // 宽度
console.log(rect.height); // 高度
console.log(rect.right); // 右边缘位置
console.log(rect.bottom); // 底边缘位置
# 10.2 元素位置
const element = document.querySelector('.box');
// offsetLeft/offsetTop:相对于offsetParent的位置
console.log(element.offsetLeft);
console.log(element.offsetTop);
console.log(element.offsetParent); // 最近的定位祖先元素
// scrollLeft/scrollTop:滚动距离
console.log(element.scrollLeft); // 水平滚动距离
console.log(element.scrollTop); // 垂直滚动距离
// 设置滚动位置
element.scrollLeft = 100;
element.scrollTop = 200;
// scrollTo:平滑滚动
element.scrollTo({
top: 500,
left: 0,
behavior: 'smooth'
});
// scrollIntoView:滚动到可见
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
# 十一、综合示例
# 十二、性能优化
# 12.1 减少DOM操作
// ✗ 避免:频繁读写DOM
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = i;
container.appendChild(div); // 触发100次重排
}
// ✓ 推荐:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
container.appendChild(fragment); // 只触发1次重排
# 12.2 批量样式修改
// ✗ 避免:逐个修改样式
element.style.width = '100px'; // 重排
element.style.height = '100px'; // 重排
element.style.background = 'red'; // 重排
// ✓ 推荐:批量修改
element.style.cssText = 'width: 100px; height: 100px; background: red;';
// ✓ 推荐:使用类名
element.classList.add('box-style');
# 12.3 缓存DOM查询
// ✗ 避免:重复查询
for (let i = 0; i < 100; i++) {
document.querySelector('.container').appendChild(div);
}
// ✓ 推荐:缓存查询结果
const container = document.querySelector('.container');
for (let i = 0; i < 100; i++) {
container.appendChild(div);
}
# 12.4 离线DOM操作
// 先从文档中移除,操作完再插入
const container = document.querySelector('.container');
const parent = container.parentNode;
parent.removeChild(container);
// 进行大量DOM操作
for (let i = 0; i < 1000; i++) {
// ...操作container
}
parent.appendChild(container);
# 十三、最佳实践
# 13.1 选择器使用
- 优先使用
getElementById(最快) - 其次使用
querySelector/querySelectorAll - 避免过于复杂的选择器
- 缩小查询范围:
element.querySelector()而不是document.querySelector()
# 13.2 内容修改
- 使用
textContent而不是innerText(性能更好) - 避免直接使用
innerHTML插入用户输入(XSS风险) - 大量内容使用
DocumentFragment
# 13.3 样式操作
- 优先使用
classList而不是className - 批量样式修改使用
cssText或类名切换 - 读取样式使用
getComputedStyle
# 13.4 事件处理
- 使用事件委托减少事件监听器数量
- 及时移除不需要的事件监听器
- 避免在循环中添加事件监听器
# 十四、总结
DOM操作是Web开发的基础技能,掌握以下要点:
- 理解DOM树结构:节点类型、节点关系
- 高效选择元素:使用现代选择器API
- 灵活操作元素:内容、属性、样式、类名
- 动态创建元素:createElement、DocumentFragment
- 性能优化:减少重排重绘、批量操作、缓存查询
通过合理使用DOM API,可以实现丰富的交互效果和动态内容更新。
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/28