JavaScript DOM操作是前端开发中实现页面动态交互的核心能力。文档对象模型将HTML文档解析为可编程的对象树,开发者通过JavaScript可以访问和修改页面中的元素、内容、属性与样式,让静态页面具备响应式交互。无论是列表渲染、表单校验、动画控制,还是单页应用的路由切换,都离不开对DOM的熟练操作。

一、认识DOM树与节点类型
DOM全称Document Object Model,即文档对象模型,它并不是JavaScript语言的一部分,而是浏览器提供的一套编程接口。当浏览器加载一个HTML文档时,会解析文档结构并生成一棵对象树,树中的每一个分支都可以视为一个节点。JavaScript通过操作这些节点对象,就能实现在不刷新页面的前提下更新界面。
在DOM树中,常见的节点类型主要包含三类:元素节点对应HTML中的标签,例如<div>、<p>、<span>等;文本节点表示元素内部包含的纯文本内容;属性节点则描述元素携带的属性,如class、id、data-*等。理解这些节点类型有助于在后续的节点查找、创建和修改过程中明确操作对象。
除节点类型外,DOM节点之间还存在父子关系与兄弟关系。一个元素节点的直接子节点可以通过childNodes或children属性访问,父节点可以通过parentNode访问,相邻节点则可以通过previousSibling和nextSibling获取。这些关系在节点遍历与插入删除操作中非常实用,能够帮助开发者精确定位目标节点。
二、获取页面元素的核心方法
在操作DOM之前,首先需要获取目标元素。JavaScript提供了一系列方法用于元素查找,其中最常用的包括getElementById、getElementsByClassName、querySelector以及querySelectorAll。不同方法适用于不同场景,选择合适的方法能提高代码的可读性和性能。
getElementById根据元素id属性查找,返回唯一的元素对象。由于id在页面中应当保持唯一,所以该方法返回单个节点;如果未找到匹配元素则返回null。与之相对,getElementsByClassName根据类名查找,返回一个实时的HTMLCollection集合,该集合类似数组但并不是真正的数组,需要通过索引或循环访问每个元素。
现代浏览器还支持querySelector和querySelectorAll,它们接受任意CSS选择器并分别返回第一个匹配元素和所有匹配元素的静态NodeList。使用CSS选择器可以更灵活地完成复杂查找,例如后代选择器、子代选择器、属性选择器等。以下代码展示了这几种方法的基本用法。
// 通过id获取单个元素
const container = document.getElementById('container');
if (container) {
console.log('获取到的容器元素:', container);
}
// 通过类名获取元素集合
const items = document.getElementsByClassName('item');
for (let i = 0; i < items.length; i++) {
console.log('第' + (i + 1) + '个item元素:', items[i]);
}
// 通过CSS选择器获取元素
const firstCard = document.querySelector('.card');
const links = document.querySelectorAll('p > a');
console.log('第一个卡片元素:', firstCard);
console.log('所有p标签下的a元素数量:', links.length);
需要注意的是,getElementsByClassName返回的HTMLCollection是动态集合,当文档结构发生变化时,集合内容会自动更新;而querySelectorAll返回的NodeList是静态集合,不会随文档变化。这一差异在遍历过程中会影响代码行为,应根据实际需求选择合适的查找方式。
三、修改元素内容、属性与样式
获取元素之后,最常见的操作就是修改元素的内容、属性和样式。修改内容可以通过innerHTML、innerText和textContent三个属性实现。它们虽然都能改变元素中的文本或结构,但行为存在明显区别。
| 属性 | 行为说明 |
|---|---|
| innerHTML | 获取或设置元素内部的HTML内容,包含标签,设置时会解析HTML标签。 |
| innerText | 获取或设置元素内部的可视文本,忽略HTML标签,结果受CSS样式影响。 |
| textContent | 获取或设置元素内部的所有文本内容,忽略HTML标签,不受CSS样式影响。 |
从上表可以看出,如果仅修改文本内容,推荐使用textContent,因为它不会触发HTML解析,性能更好且安全性更高。而innerHTML适合需要插入HTML结构的场景,但需要注意避免将用户输入未经处理直接赋值,否则可能引入安全风险。以下示例演示了内容修改和属性修改的典型用法。
const title = document.getElementById('title');
// 修改纯文本内容
title.textContent = '新的标题内容';
// 设置包含HTML标签的内容,<em>会被解析为斜体
title.innerHTML = '新的<em>标题</em>内容';
// 修改图片元素的属性
const img = document.querySelector('img');
img.src = 'https://ipipp.com/new-img.png';
img.alt = '新图片';
// 操作自定义属性
img.setAttribute('data-id', '1001');
const dataId = img.getAttribute('data-id');
console.log('自定义属性data-id的值:', dataId);
修改样式则主要依赖style属性。style对象对应元素的内联样式,通过设置style对象中的属性可以动态改变元素的展示效果。对于CSS中多单词属性名称,在JavaScript中需转换为驼峰命名,例如background-color应写为backgroundColor,font-size应写为fontSize。下面是一个简单的样式修改示例。
const box = document.getElementById('box');
// 修改背景色
box.style.backgroundColor = '#f0f0f0';
// 修改尺寸
box.style.width = '200px';
box.style.height = '200px';
// 修改边框样式
box.style.border = '1px solid #ccc';
四、动态创建、插入与替换节点
除了修改已有元素,DOM操作还支持在页面中动态创建、插入、删除和替换节点。创建元素节点使用document.createElement,创建文本节点使用document.createTextNode。创建完成后,新节点通常需要通过添加方法挂载到文档中才会显示。
添加节点的常用方法是appendChild,它会把指定节点追加到父元素的子节点列表末尾。如果需要将节点插入到某个子节点之前,则可以使用insertBefore。删除节点使用removeChild,替换节点使用replaceChild。以下代码演示了创建、添加、删除和替换的完整流程。
// 创建元素节点和文本节点
const newDiv = document.createElement('div');
const textNode = document.createTextNode('这是新添加的div元素');
newDiv.appendChild(textNode);
newDiv.className = 'new-item';
// 将新节点添加到父元素末尾
const parent = document.getElementById('parent');
parent.appendChild(newDiv);
// 创建参考节点,将新节点插入到参考节点之前
const referenceNode = document.getElementById('reference');
const anotherDiv = document.createElement('div');
anotherDiv.textContent = '插入到参考节点之前的元素';
parent.insertBefore(anotherDiv, referenceNode);
// 创建替换节点
const replaceDiv = document.createElement('div');
replaceDiv.textContent = '替换后的元素';
// 用replaceDiv替换anotherDiv
parent.replaceChild(replaceDiv, anotherDiv);
// 删除替换后的节点
parent.removeChild(replaceDiv);
在进行批量插入操作时,频繁调用appendChild或insertBefore会导致浏览器多次重排,从而影响页面性能。此时可以使用DocumentFragment作为临时容器,将新节点先添加到文档片段中,最后一次性将文档片段添加到目标父元素,从而减少DOM操作次数。
五、事件绑定与交互响应
DOM操作的最终目标是响应用户交互。JavaScript通过事件机制实现交互响应,常见的事件类型包括鼠标事件、键盘事件和表单事件。鼠标事件如click、mouseover、mouseout分别对应用户点击、鼠标移入和移出;键盘事件如keydown、keyup对应按键按下和抬起;表单事件如input、submit对应输入变化和表单提交。
绑定事件推荐使用addEventListener方法。与传统的onclick等属性相比,addEventListener支持为同一元素绑定多个同类型事件处理函数,并且不会覆盖之前已绑定的函数。此外,它还允许通过第三个参数控制事件捕获或冒泡阶段。以下示例展示了按钮点击事件的绑定方式。
const btn = document.getElementById('submitBtn');
// 绑定第一个点击事件
btn.addEventListener('click', function(event) {
// 阻止默认行为,例如阻止表单提交后的页面跳转
event.preventDefault();
console.log('按钮被点击了');
alert('提交成功');
});
// 绑定第二个点击事件,不会覆盖前一个
btn.addEventListener('click', function() {
console.log('第二个点击事件处理函数');
});
对于动态创建的元素,直接在元素创建后绑定事件是可行的;但如果元素是后续异步加载的,可能需要使用事件委托,将事件绑定在静态父元素上,通过判断事件目标来执行逻辑。事件委托可以减少内存占用并简化代码
下面通过一个列表点击的例子来演示事件委托的具体用法。将点击事件绑定在列表容器上,通过 event.target 判断实际点击的元素,可以避免为每个列表项单独绑定事件。
const list = document.getElementById('itemList');
list.addEventListener('click', function(event) {
const target = event.target;
// 判断点击的是否是列表项
if (target.tagName === 'LI') {
console.log('点击了列表项:', target.textContent);
target.classList.toggle('active');
}
});
如果列表项内部还包含其他元素,比如链接、图标或文本节点,event.target 可能指向这些内部元素,而不是 li 本身。这时可以使用 closest 方法向上查找最近的匹配元素,从而准确获取列表项。
list.addEventListener('click', function(event) {
const li = event.target.closest('li');
if (li && list.contains(li)) {
console.log('点击的列表项:', li.textContent);
li.classList.toggle('active');
}
});
事件委托除了减少事件监听器的数量,还能自动兼容动态添加的元素。因为事件绑定在父元素上,即使之后通过 JavaScript 新增了子元素,新元素同样会触发父元素上的事件处理函数,无需重新绑定。
有时需要阻止事件继续向上冒泡,避免触发父元素上的其他事件处理逻辑。比如在列表项中放置一个删除按钮,点击删除按钮时只执行删除操作,而不触发列表项本身的选中效果。此时可以调用 event.stopPropagation()。
list.addEventListener('click', function(event) {
const li = event.target.closest('li');
if (li) {
console.log('列表项被点击');
}
});
// 动态创建的删除按钮也可以使用事件委托
list.addEventListener('click', function(event) {
if (event.target.classList.contains('delete-btn')) {
event.stopPropagation();
const li = event.target.closest('li');
if (li) {
li.remove();
console.log('列表项已删除');
}
}
});
在上面的代码中,同一个列表容器绑定了两个点击事件。点击删除按钮时,第一个事件处理函数会因为冒泡而触发,但第二个事件处理函数在检测到删除按钮后会阻止冒泡,避免第一个处理函数继续执行。不过由于两个监听器都绑定在同一个元素上,stopPropagation 并不能阻止同一元素上的其他监听器执行。如果需要阻止同一元素上的其他监听器,可以使用 event.stopImmediatePropagation()。
六、表单事件与输入处理
表单事件是用户输入交互的核心。input 事件在输入框内容发生变化时实时触发,非常适合做即时校验、字符计数或搜索提示。change 事件在元素值改变并且失去焦点后触发,适用于下拉框选择或文件上传等场景。submit 事件在表单提交时触发,通常用于前端校验和异步提交数据。
下面是一个常见的表单校验示例,包含用户名实时提示和表单提交拦截。
const usernameInput = document.getElementById('username');
const feedback = document.getElementById('usernameFeedback');
const loginForm = document.getElementById('loginForm');
usernameInput.addEventListener('input', function(event) {
const value = event.target.value;
if (value.length < 3) {
feedback.textContent = '用户名至少需要3个字符';
feedback.style.color = 'red';
} else {
feedback.textContent = '用户名可用';
feedback.style.color = 'green';
}
});
loginForm.addEventListener('submit', function(event) {
event.preventDefault();
const username = usernameInput.value.trim();
if (username === '') {
alert('用户名不能为空');
return;
}
console.log('提交表单,用户名:', username);
// 实际项目中可使用fetch或XMLHttpRequest发送数据到服务器
});
键盘事件常用于快捷键操作、输入限制或实时搜索。例如按下回车键触发搜索、按下 Esc 键关闭弹窗等。keydown 事件在按键按下时触发,keyup 事件在按键松开时触发。通过 event.key 可以获取按键的字符串值,例如 'Enter'、'Escape' 等。
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
console.log('执行搜索:', searchInput.value);
// 调用搜索函数
}
if (event.key === 'Escape') {
searchInput.value = '';
console.log('已清空搜索框');
}
});
七、DOM操作性能优化
频繁的 DOM 读写操作会导致页面多次重排和重绘,影响性能。掌握一些实用的优化技巧,可以有效提升页面交互的流畅度。
首先,合并 DOM 操作。使用文档片段 DocumentFragment 或先拼接 HTML 字符串,最后一次插入到文档中,避免在循环中直接操作 DOM。其次,缓存 DOM 引用。需要多次使用同一个元素时,应该将其保存在变量中,而不是反复通过 document.getElementById 等方法查询。
// 不推荐的写法:多次查询同一个元素
document.getElementById('box').style.width = '100px';
document.getElementById('box').style.height = '100px';
document.getElementById('box').style.backgroundColor = 'red';
// 推荐的写法:缓存元素引用
const box = document.getElementById('box');
box.style.width = '100px';
box.style.height = '100px';
box.style.backgroundColor = 'red';
另外,尽量使用 CSS 类来控制样式,而不是逐个修改 style 属性。例如切换一个 active 类来改变元素外观,可以减少内联样式操作,也更有利于维护。
// 切换类名
const menu = document.getElementById('menu');
menu.classList.toggle('open');
对于需要频繁触发的事件,例如 scroll、resize 或输入框的 input,可以使用节流(throttle)或防抖(debounce)技术来限制事件处理函数的执行频率。防抖通常用于搜索输入,用户停止输入一段时间后才发起请求;节流适用于滚动加载等场景,保证一定时间间隔内只执行一次。
// 简单防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
searchInput.addEventListener('input', debounce(function() {
console.log('发起搜索请求:', searchInput.value);
}, 300));
八、总结
DOM 操作与事件绑定是前端开发的基础能力。通过合理选择 DOM 查询方法、掌握节点增删改查技巧、利用文档片段优化批量操作、理解事件冒泡与委托机制,能够编写出更高效、更易维护的代码。同时,结合实际场景运用表单事件、键盘事件以及性能优化策略,可以显著提升用户交互体验。
这些基础概念不仅在原生 JavaScript 开发中至关重要,也为理解现代前端框架如 React、Vue 的虚拟 DOM 与合成事件机制提供了底层支撑。希望本文的讲解和示例能够帮助你在实际项目中更加熟练地操作 DOM、处理用户交互,并写出性能更优的前端代码。
JavaScriptDOM前端开发DOM操作修改时间:2026-07-19 10:27:42