1. DOM操作基础与核心概念
DOM(Document Object Model)是前端开发中最基础也最重要的概念之一。简单来说,DOM就是浏览器将HTML文档解析成的一个树状结构,每个HTML标签都对应DOM中的一个节点。通过JavaScript操作这些节点,我们就能动态改变网页的内容、结构和样式。
在实际项目中,DOM操作几乎无处不在:表单验证、动态加载内容、交互效果实现等都离不开它。但很多初学者容易陷入一个误区——过度依赖jQuery等库来进行DOM操作。其实现代浏览器提供的原生API已经足够强大,掌握它们能让你写出更高效、更可控的代码。
提示:虽然jQuery简化了DOM操作,但在现代前端开发中,直接使用原生API已经成为主流。这不仅能减少依赖,还能提升性能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 节点查询与选择
2.1 基本选择方法
获取DOM元素是操作的第一步,最常用的方法有:
javascript复制// 通过ID获取(返回单个元素)
const header = document.getElementById('header');
// 通过类名获取(返回HTMLCollection)
const buttons = document.getElementsByClassName('btn');
// 通过标签名获取(返回HTMLCollection)
const images = document.getElementsByTagName('img');
// 通过CSS选择器获取(返回第一个匹配元素)
const mainBtn = document.querySelector('.primary-btn');
// 通过CSS选择器获取所有匹配元素(返回NodeList)
const allBtns = document.querySelectorAll('.btn');
在实际开发中,querySelector和querySelectorAll是最灵活的选择方式,它们支持所有CSS选择器语法。但要注意,getElementsByClassName和getElementsByTagName返回的是动态集合(HTMLCollection),而querySelectorAll返回的是静态集合(NodeList)。
2.2 选择器性能优化
当页面元素很多时,选择器的性能差异就会显现。一般来说:
- ID选择器最快,因为浏览器有专门的优化
- 类选择器次之
- 标签选择器再次之
- 复杂选择器(如
div > p + a[href^="https"])最慢
经验:在循环中频繁查询DOM会严重影响性能。应该先获取元素引用,然后在循环中使用这些引用。
3. 元素内容与属性操作
3.1 内容操作
修改元素内容主要有三种方式:
javascript复制const div = document.querySelector('#content');
// 1. innerHTML - 可以包含HTML标签
div.innerHTML = '<strong>新内容</strong>';
// 2. textContent - 纯文本,更安全
div.textContent = '新内容';
// 3. innerText - 考虑样式,性能较差
div.innerText = '新内容';
innerHTML虽然强大,但存在XSS攻击风险。如果内容来自用户输入,一定要先进行转义处理,或者使用textContent。
3.2 属性操作
元素属性操作有两种主要方式:
javascript复制const link = document.querySelector('a');
// 获取属性
const href = link.getAttribute('href');
// 设置属性
link.setAttribute('target', '_blank');
// 移除属性
link.removeAttribute('title');
// 直接访问属性(仅适用于标准属性)
link.id = 'new-id';
link.className = 'btn primary'; // 注意是className不是class
对于自定义属性(data-*),可以使用dataset:
javascript复制// <div data-user-id="123"></div>
const div = document.querySelector('div');
console.log(div.dataset.userId); // "123"
div.dataset.userId = '456';
4. 样式操作
4.1 直接修改样式
通过style属性可以直接修改元素的内联样式:
javascript复制const box = document.getElementById('box');
// 单个样式设置
box.style.color = 'red';
box.style.backgroundColor = '#fff'; // 注意驼峰命名
// 批量设置
box.style.cssText = 'color: red; background: #fff; width: 100px;';
// 移除样式
box.style.color = '';
注意:通过style属性设置的样式优先级很高,会覆盖外部CSS中的样式(除非有!important)。
4.2 类名操作
更推荐的方式是通过添加/移除类名来改变样式:
javascript复制const element = document.querySelector('.item');
// 添加类
element.classList.add('active');
// 移除类
element.classList.remove('active');
// 切换类
element.classList.toggle('active');
// 检查是否包含类
if (element.classList.contains('active')) {
// ...
}
相比直接操作className属性,classList方法更灵活,也不会影响元素的其他类名。
5. DOM节点操作
5.1 创建与添加节点
javascript复制// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = '我是新创建的div';
// 添加到DOM中
document.body.appendChild(newDiv);
// 插入到特定位置
const parent = document.querySelector('.container');
const firstChild = parent.firstElementChild;
parent.insertBefore(newDiv, firstChild); // 插入到第一个子元素前
5.2 节点删除与替换
javascript复制// 删除节点
const oldDiv = document.querySelector('.old');
oldDiv.parentNode.removeChild(oldDiv);
// 更现代的删除方式
oldDiv.remove();
// 替换节点
const newParagraph = document.createElement('p');
newParagraph.textContent = '新段落';
oldDiv.replaceWith(newParagraph);
5.3 克隆节点
javascript复制const original = document.querySelector('.template');
const clone = original.cloneNode(true); // true表示深度克隆
document.body.appendChild(clone);
6. 事件处理
6.1 事件监听
现代的事件监听方式:
javascript复制const button = document.querySelector('button');
function handleClick(event) {
console.log('按钮被点击了', event);
}
// 添加事件监听
button.addEventListener('click', handleClick);
// 移除事件监听
button.removeEventListener('click', handleClick);
6.2 事件委托
当需要给多个元素添加相同事件时,使用事件委托可以提升性能:
javascript复制document.querySelector('.list').addEventListener('click', function(event) {
if (event.target.classList.contains('item')) {
console.log('点击了项目', event.target);
}
});
这种方式只需要一个事件监听器,而不是给每个.item元素都添加一个。
7. 性能优化技巧
7.1 减少DOM操作
DOM操作是昂贵的,应该尽量减少:
javascript复制// 不好:多次修改DOM
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
// 好:使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
document.body.appendChild(fragment);
7.2 避免强制同步布局
连续读取和修改样式属性会导致浏览器频繁重排:
javascript复制// 不好:导致强制同步布局
element.style.width = '100px';
const height = element.offsetHeight; // 读取
element.style.height = height + 'px'; // 写入
// 好:批量读写
element.style.width = '100px';
const height = element.offsetHeight;
requestAnimationFrame(() => {
element.style.height = height + 'px';
});
8. 常见问题与解决方案
8.1 动态元素事件失效
问题:通过JS动态添加的元素,之前绑定的事件不生效。
解决方案:使用事件委托,或者在添加元素后重新绑定事件。
8.2 大量DOM操作导致卡顿
问题:一次性操作大量DOM元素导致页面卡顿。
解决方案:
- 使用文档片段(document.createDocumentFragment)
- 分批处理(setTimeout或requestAnimationFrame)
- 考虑虚拟DOM技术
8.3 内存泄漏
问题:移除元素后,相关事件监听器没有移除,导致内存泄漏。
解决方案:在移除元素前,先移除其事件监听器。
javascript复制// 添加事件时使用命名函数
function handleClick() { /*...*/ }
element.addEventListener('click', handleClick);
// 移除元素前
element.removeEventListener('click', handleClick);
element.remove();
9. 现代DOM操作实践
9.1 Intersection Observer API
替代传统的滚动监听,更高效地检测元素可见性:
javascript复制const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('元素进入视口', entry.target);
}
});
});
observer.observe(document.querySelector('.lazy-load'));
9.2 Mutation Observer API
监听DOM变化:
javascript复制const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
console.log('DOM发生了变化', mutation);
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
10. 实战案例:构建一个简单的待办事项应用
让我们用学到的DOM操作知识构建一个功能完整的待办事项应用:
html复制<!DOCTYPE html>
<html>
<head>
<title>待办事项</title>
<style>
.completed {
text-decoration: line-through;
color: #999;
}
</style>
</head>
<body>
<h1>我的待办事项</h1>
<input type="text" id="newTodo" placeholder="输入新任务">
<button id="addBtn">添加</button>
<ul id="todoList"></ul>
<script>
const todoInput = document.getElementById('newTodo');
const addBtn = document.getElementById('addBtn');
const todoList = document.getElementById('todoList');
// 添加新任务
function addTodo() {
const text = todoInput.value.trim();
if (!text) return;
const li = document.createElement('li');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.addEventListener('change', toggleTodo);
const span = document.createElement('span');
span.textContent = text;
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.addEventListener('click', deleteTodo);
li.appendChild(checkbox);
li.appendChild(span);
li.appendChild(deleteBtn);
todoList.appendChild(li);
todoInput.value = '';
}
// 切换完成状态
function toggleTodo(e) {
const li = e.target.parentElement;
li.classList.toggle('completed');
}
// 删除任务
function deleteTodo(e) {
const li = e.target.parentElement;
li.remove();
}
// 事件监听
addBtn.addEventListener('click', addTodo);
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTodo();
});
</script>
</body>
</html>
这个示例涵盖了DOM操作的大部分核心概念:元素创建、添加、删除、事件监听、样式修改等。在实际项目中,你可能会使用框架来管理这些操作,但理解底层原理仍然非常重要。
