1. JavaScript DOM 操作基础概念
DOM(Document Object Model)是网页与JavaScript交互的核心接口。简单来说,它就像是一个家族树,把HTML文档中的每个元素、属性和文本都表示为节点对象。理解DOM结构是进行任何前端开发的基础。
1.1 DOM节点类型详解
DOM中主要有四种节点类型:
- 元素节点:对应HTML标签,如
<div>、<p>等。这是最常见的节点类型。 - 文本节点:包含在元素节点内的文本内容,包括空格和换行符。
- 属性节点:元素的属性,如
id、class等。 - 注释节点:HTML中的注释内容。
在实际开发中,我们最常操作的是元素节点和文本节点。属性节点虽然存在,但通常不直接作为独立节点操作。
1.2 节点关系解析
理解节点间的关系至关重要:
- parentNode:每个节点(除了document)都有一个父节点
- childNodes:包含所有子节点(包括文本节点和注释节点)
- children:只包含元素子节点
- firstChild/lastChild:第一个/最后一个子节点
- nextSibling/previousSibling:相邻的兄弟节点
注意:childNodes和children的区别经常被忽视。childNodes包含所有类型的节点,而children只包含元素节点。这在遍历DOM时尤其重要。
2. 元素获取方法全解析
2.1 传统获取方法
javascript复制// 通过ID获取(返回单个元素)
const elementById = document.getElementById('header');
// 通过类名获取(返回HTMLCollection)
const elementsByClass = document.getElementsByClassName('item');
// 通过标签名获取(返回HTMLCollection)
const elementsByTag = document.getElementsByTagName('div');
这些传统方法仍然有效,但有局限性:
- 只能通过单一条件筛选
- 返回的HTMLCollection是"活的"集合,DOM变化时会自动更新
- 性能略优于querySelector系列
2.2 现代选择器方法
javascript复制// 获取第一个匹配元素
const firstMatch = document.querySelector('.container > .item');
// 获取所有匹配元素(返回NodeList)
const allMatches = document.querySelectorAll('div.item');
querySelector系列的优势:
- 支持所有CSS选择器语法
- 可以组合复杂条件
- 返回的NodeList是静态的(不会随DOM变化自动更新)
性能提示:在简单选择场景下,getElementById比querySelector快约15-20%。但在复杂选择时,querySelector更高效。
3. 内容操作深度指南
3.1 textContent vs innerText
javascript复制const div = document.querySelector('div');
// textContent获取所有文本内容,包括隐藏元素
console.log(div.textContent);
// innerText考虑CSS样式,只返回可见文本
console.log(div.innerText);
关键区别:
textContent获取所有后代文本节点innerText考虑CSS渲染,不返回display:none的元素内容innerText会触发回流(reflow),性能较差
3.2 innerHTML的安全考量
javascript复制// 设置HTML内容
element.innerHTML = '<strong>警告</strong>:重要通知';
// 危险示例(XSS攻击)
const userInput = '<img src=x onerror=alert("hacked")>';
element.innerHTML = userInput; // 会执行恶意脚本
安全实践:
- 永远不要直接将用户输入赋值给innerHTML
- 使用textContent处理纯文本
- 必须使用时,先转义特殊字符或使用DOMPurify等库净化
4. 属性操作最佳实践
4.1 标准属性操作
javascript复制const img = document.querySelector('img');
// 直接访问属性
img.src = 'new-image.jpg';
img.alt = '替代文本';
// 通用方法
img.setAttribute('data-id', '123');
const id = img.getAttribute('data-id');
img.removeAttribute('data-id');
4.2 classList高级用法
javascript复制const div = document.querySelector('div');
// 基本操作
div.classList.add('active');
div.classList.remove('inactive');
div.classList.toggle('visible');
// 高级用法
div.classList.replace('old', 'new');
// 条件判断
if (div.classList.contains('hidden')) {
// 执行操作
}
技巧:classList方法比直接操作className字符串更高效,且能避免类名冲突问题。
5. 节点创建与管理
5.1 创建节点
javascript复制// 创建元素节点
const newDiv = document.createElement('div');
// 创建文本节点
const textNode = document.createTextNode('Hello World');
// 创建文档片段(优化性能)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('div');
fragment.appendChild(item);
}
document.body.appendChild(fragment);
5.2 节点操作
javascript复制const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
const newChild = document.createElement('div');
// 添加节点
parent.appendChild(newChild);
parent.insertBefore(newChild, child);
// 替换节点
parent.replaceChild(newChild, child);
// 移除节点
parent.removeChild(child);
// 现代方法
child.remove();
性能提示:频繁操作DOM会导致浏览器多次重排(reflow),应尽量减少直接DOM操作次数。使用文档片段(documentFragment)或先移除元素再批量添加可以优化性能。
6. 样式操作详解
6.1 内联样式
javascript复制const div = document.querySelector('div');
// 设置单个样式
div.style.color = 'red';
// 设置多个样式
div.style.cssText = 'color: red; background: blue;';
// 获取样式(只能获取内联样式)
console.log(div.style.color);
6.2 计算样式
javascript复制// 获取最终计算样式(包括CSS样式表中的样式)
const style = window.getComputedStyle(div);
console.log(style.color);
注意:直接修改style属性会添加内联样式,优先级最高。通常推荐使用classList操作类名来管理样式。
7. 事件处理高级技巧
7.1 事件监听
javascript复制const button = document.querySelector('button');
// 添加事件监听
function handleClick(event) {
console.log('Clicked', event.target);
}
button.addEventListener('click', handleClick);
// 移除事件监听
button.removeEventListener('click', handleClick);
7.2 事件委托
javascript复制const list = document.querySelector('ul');
// 使用事件委托处理动态列表
list.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('List item clicked:', event.target.textContent);
}
});
事件委托优势:
- 减少事件监听器数量,提高性能
- 自动处理动态添加的子元素
- 简化代码结构
8. 性能优化实践
8.1 减少DOM操作
javascript复制// 不好的做法
for (let i = 0; i < 100; i++) {
document.body.appendChild(document.createElement('div'));
}
// 好的做法
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);
8.2 缓存DOM查询结果
javascript复制// 不好的做法
for (let i = 0; i < 10; i++) {
document.querySelector('.item').style.color = 'red';
}
// 好的做法
const item = document.querySelector('.item');
for (let i = 0; i < 10; i++) {
item.style.color = 'red';
}
8.3 批量样式修改
javascript复制// 不好的做法
element.style.margin = '10px';
element.style.padding = '15px';
element.style.color = 'red';
// 好的做法
element.style.cssText = 'margin: 10px; padding: 15px; color: red;';
// 更好的做法(使用类名)
element.classList.add('active-style');
9. 常见问题与解决方案
9.1 脚本执行时机问题
javascript复制// 错误:元素尚未加载
document.querySelector('button').addEventListener('click', handleClick);
// 正确:等待DOM加载完成
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('button').addEventListener('click', handleClick);
});
9.2 动态元素事件处理
javascript复制// 传统方法(对新添加元素无效)
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// 使用事件委托(对所有现有和未来元素有效)
document.querySelector('.container').addEventListener('click', function(event) {
if (event.target.classList.contains('item')) {
handleClick(event);
}
});
9.3 内存泄漏预防
javascript复制// 添加事件监听
function handleClick() {
console.log('Clicked');
}
button.addEventListener('click', handleClick);
// 移除元素前必须移除事件监听
button.removeEventListener('click', handleClick);
button.remove();
10. 现代DOM操作API
10.1 Element.matches()
javascript复制// 检查元素是否匹配选择器
if (element.matches('.active.item')) {
// 执行操作
}
10.2 Element.closest()
javascript复制// 查找最近的祖先元素
const parent = element.closest('.container');
10.3 观察DOM变化
javascript复制// 创建观察者
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log('DOM changed', mutation);
});
});
// 配置并启动观察
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 停止观察
observer.disconnect();
在实际项目中,我经常发现开发者过度依赖jQuery等库来进行DOM操作。随着现代JavaScript的发展,原生API已经足够强大且性能更好。掌握这些核心DOM操作技巧,不仅能提高代码效率,还能减少不必要的依赖。特别是在开发大型应用时,原生DOM操作的性能优势会更加明显。