什么是DOM?
DOM(Document Object Model)是HTML和XML文档的编程接口,它将文档表示为树形结构,允许程序动态访问和更新文档的内容、结构和样式。
DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。这种结构化的表示允许开发者使用编程语言(如JavaScript)来操作文档的结构、样式和内容。
DOM树结构
DOM将HTML文档表示为树状结构,其中:
- 整个文档是一个文档节点
- 每个HTML元素是一个元素节点
- HTML元素内的文本是文本节点
- 每个HTML属性是一个属性节点
- 注释是注释节点
HTML文档示例
<!DOCTYPE html>
<html>
<head>
<title>我的网页</title>
</head>
<body>
<h1>我的标题</h1>
<p>我的段落</p>
</body>
</html>
上述HTML对应的DOM树结构如下:
- 文档节点 (Document)
- HTML元素节点 (html)
- HEAD元素节点 (head)
- TITLE元素节点 (title)
- 文本节点 ("我的网页")
- BODY元素节点 (body)
- H1元素节点 (h1)
- 文本节点 ("我的标题")
- P元素节点 (p)
- 文本节点 ("我的段落")
选择DOM元素
1. 传统选择方法
这些是早期DOM标准提供的方法,至今仍然广泛使用:
传统选择器
// 通过ID选择 - 返回单个元素
const element = document.getElementById('myId');
// 通过类名选择 - 返回HTMLCollection(实时集合)
const elements = document.getElementsByClassName('myClass');
// 通过标签名选择 - 返回HTMLCollection(实时集合)
const divs = document.getElementsByTagName('div');
// 通过name属性选择 - 返回NodeList(实时集合)
const inputs = document.getElementsByName('username');
// 通过表单名称选择
const form = document.forms['myForm'];
提示: getElementsByClassName 和 getElementsByTagName 返回的是HTMLCollection,它是实时更新的。当文档发生变化时,这些集合会自动更新。
2. 现代选择方法(推荐)
querySelector和querySelectorAll方法提供了更强大的CSS选择器支持:
querySelector方法
// 选择单个元素
const element = document.querySelector('#myId');
const element = document.querySelector('.myClass');
const element = document.querySelector('div');
const element = document.querySelector('input[type="text"]');
// 选择多个元素 - 返回NodeList(静态集合)
const elements = document.querySelectorAll('.myClass');
const elements = document.querySelectorAll('div, p');
const elements = document.querySelectorAll('.container > .item');
// 在特定元素内选择
const container = document.getElementById('container');
const items = container.querySelectorAll('.item');
提示: querySelectorAll 返回的是NodeList,它是静态的,不会随文档变化自动更新。
3. 特殊元素选择
特殊元素选择
// 文档根元素
const html = document.documentElement;
// 文档body元素
const body = document.body;
// 文档head元素
const head = document.head;
// 当前活动元素
const active = document.activeElement;
// 所有图片
const images = document.images;
// 所有链接
const links = document.links;
// 所有表单
const forms = document.forms;
修改元素内容
内容操作
const element = document.getElementById('myElement');
// 修改文本内容(推荐,更安全)
element.textContent = '新的文本内容';
// 修改HTML内容(注意XSS风险)
element.innerHTML = '<strong>加粗文本</strong>';
// 获取内容
const text = element.textContent;
const html = element.innerHTML;
// 设置属性
element.setAttribute('data-custom', 'value');
const value = element.getAttribute('data-custom');
// 移除属性
element.removeAttribute('data-custom');
// 检查属性是否存在
if (element.hasAttribute('data-custom')) {
console.log('元素有data-custom属性');
}
// 直接访问属性(标准属性)
element.id = 'newId';
element.className = 'new-class';
element.title = '新标题';
// 使用classList操作类名(推荐)
element.classList.add('new-class');
element.classList.remove('old-class');
element.classList.toggle('active');
element.classList.contains('some-class');
警告: 使用innerHTML插入用户提供的内容时存在XSS(跨站脚本攻击)风险。请确保对用户输入进行适当的清理和转义,或考虑使用textContent。
innerHTML vs textContent vs innerText
| 属性 | 描述 | 性能 | XSS风险 |
|---|---|---|---|
| innerHTML | 获取或设置元素的HTML内容 | 慢 | 高 |
| textContent | 获取或设置元素及其后代的文本内容 | 快 | 无 |
| innerText | 获取或设置元素的"渲染"文本内容 | 最慢 | 无 |
操作元素样式
样式操作
const element = document.getElementById('myElement');
// 直接修改样式(驼峰命名)
element.style.color = 'red';
element.style.backgroundColor = '#f0f0f0';
element.style.fontSize = '16px';
element.style.display = 'none';
// 批量设置样式
element.style.cssText = 'color: red; background: blue;';
// 添加/移除CSS类(推荐方式)
element.classList.add('active');
element.classList.remove('inactive');
element.classList.toggle('visible');
// 检查类是否存在
if (element.classList.contains('hidden')) {
element.classList.remove('hidden');
}
// 获取计算样式
const computedStyle = window.getComputedStyle(element);
const color = computedStyle.color;
const fontSize = computedStyle.fontSize;
// 获取元素尺寸和位置
const rect = element.getBoundingClientRect();
console.log(rect.width, rect.height, rect.top, rect.left);
样式操作最佳实践
- 优先使用classList操作类名,而不是直接修改style属性
- 使用CSS类来管理样式,使用JavaScript来切换类
- 避免频繁修改样式,考虑使用requestAnimationFrame进行批量更新
- 使用getBoundingClientRect()获取元素精确位置和尺寸
创建和插入元素
元素创建和插入
// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = '新创建的元素';
newDiv.className = 'new-element';
// 创建文本节点
const textNode = document.createTextNode('一些文本');
// 创建文档片段(优化性能)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('div');
item.textContent = `项目 ${i}`;
fragment.appendChild(item);
}
// 插入元素的不同方式
const parent = document.getElementById('parent');
// 末尾插入
parent.appendChild(newDiv);
// 开头插入
parent.insertBefore(newDiv, parent.firstChild);
// 在指定元素前插入
const referenceElement = document.getElementById('reference');
parent.insertBefore(newDiv, referenceElement);
// 替换元素
parent.replaceChild(newDiv, oldElement);
// 移除元素
parent.removeChild(elementToRemove);
// 现代方法:remove()(不需要父元素)
elementToRemove.remove();
// 克隆元素
const clonedElement = element.cloneNode(true); // true表示深度克隆
性能提示: 当需要添加多个DOM元素时,使用DocumentFragment可以显著提高性能,因为它不会触发重排,直到将片段添加到文档中。
遍历DOM树
DOM遍历方法
const element = document.getElementById('myElement');
// 父节点
const parent = element.parentNode;
const parentElement = element.parentElement;
// 子节点
const children = element.children; // 只包含元素节点
const childNodes = element.childNodes; // 包含所有类型节点
const firstChild = element.firstChild; // 第一个子节点(任何类型)
const lastChild = element.lastChild; // 最后一个子节点(任何类型)
// 元素子节点
const firstElementChild = element.firstElementChild;
const lastElementChild = element.lastElementChild;
// 兄弟节点
const nextSibling = element.nextSibling; // 下一个兄弟节点(任何类型)
const previousSibling = element.previousSibling; // 上一个兄弟节点(任何类型)
const nextElementSibling = element.nextElementSibling;
const prevElementSibling = element.previousElementSibling;
// 查找特定子元素
const specificChild = element.querySelector('.specific-class');
const specificChildren = element.querySelectorAll('.specific-class');
// 检查节点关系
if (parent.contains(element)) {
console.log('元素在父元素内');
}
if (element.hasChildNodes()) {
console.log('元素有子节点');
}
// 遍历子节点
for (let i = 0; i < element.children.length; i++) {
console.log(element.children[i]);
}
// 使用for...of循环(现代方式)
for (const child of element.children) {
console.log(child);
}
DOM操作性能优化
1. 批量DOM操作
使用DocumentFragment
// 低效方式 - 每次都会触发重排
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `项目 ${i}`;
document.body.appendChild(div);
}
// 高效方式 - 使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `项目 ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
2. 使用事件委托
事件委托示例
// 低效方式 - 为每个元素添加监听器
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// 高效方式 - 使用事件委托
document.getElementById('container').addEventListener('click', function(e) {
if (e.target.classList.contains('item')) {
handleClick(e);
}
});
3. 避免强制同步布局
避免布局抖动
// 不好的做法 - 导致布局抖动
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
// 好的做法 - 批量读取和写入
const width = box.offsetWidth;
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
实践练习
原始内容区域
嵌套元素1
嵌套元素2
综合DOM操作示例
// 创建购物车项目列表
function addCartItem(name, price) {
const cart = document.getElementById('cart');
// 创建新项目
const item = document.createElement('div');
item.className = 'cart-item';
item.dataset.price = price; // 使用data属性存储数据
// 设置内容
item.innerHTML = `
<span class="item-name">${name}</span>
<span class="item-price">¥${price}</span>
<button class="remove-btn">删除</button>
`;
// 添加删除功能
const removeBtn = item.querySelector('.remove-btn');
removeBtn.addEventListener('click', function() {
cart.removeChild(item);
updateTotal();
});
// 添加到购物车
cart.appendChild(item);
updateTotal();
}
// 更新总价
function updateTotal() {
const items = document.querySelectorAll('.item-price');
let total = 0;
items.forEach(item => {
total += parseFloat(item.textContent.replace('¥', ''));
});
document.getElementById('total').textContent = `总计: ¥${total}`;
}
// 动态表单验证
function setupFormValidation() {
const form = document.getElementById('myForm');
const emailInput = document.getElementById('email');
emailInput.addEventListener('input', function() {
if (this.validity.typeMismatch) {
this.setCustomValidity('请输入有效的电子邮件地址');
} else {
this.setCustomValidity('');
}
});
form.addEventListener('submit', function(e) {
if (!form.checkValidity()) {
e.preventDefault();
alert('请正确填写所有字段');
}
});
}
浏览器兼容性
Chrome
1.0+ (完全支持)
Firefox
1.0+ (完全支持)
Safari
1.0+ (完全支持)
Edge
12.0+ (完全支持)
IE
6.0+ (基本支持),9.0+ (完全支持)