JavaScript DOM 操作:选择器、事件与节点修改
JavaScript DOM 操作:选择器、事件与节点修改
DOM(文档对象模型)是浏览器将 HTML 文档解析为一个由节点和对象组成的树形结构。JavaScript 通过 DOM 可以实现对页面内容、结构和样式的动态控制,这是构建交互式网页的核心能力。本篇指南将系统介绍选择器、事件绑定与节点修改三大核心操作。
1. 核心概念:什么是 DOM?
浏览器在加载 HTML 页面后,会生成一个对应的 DOM 树。每个标签、属性、文本都成为树上的节点(Node),常见节点类型有:
- 元素节点(
element,对应 HTML 标签) - 文本节点(
text,标签内的文字) - 属性节点(
attribute,已废弃,现通过元素直接访问)
JavaScript 通过全局对象 document 来操作整个 DOM 树。
2. 选择器:精准定位页面元素
要操作某个元素,第一步是获取它的引用。DOM 提供了多种选择方法,按灵活性和性能可分为传统方法和现代方法。
2.1 传统选择方法(速度快、兼容性好)
| 方法 | 描述 | 返回值 |
|---|---|---|
document.getElementById(id) |
通过 id 属性选取元素 |
单个元素 或 null |
document.getElementsByClassName(cls) |
通过类名选取元素 | 动态 HTMLCollection |
document.getElementsByTagName(tag) |
通过标签名选取元素 | 动态 HTMLCollection |
document.getElementsByName(name) |
通过 name 属性选取元素(常用于表单) |
动态 NodeList |
示例:
const header = document.getElementById('main-header');
const buttons = document.getElementsByClassName('btn');
const allDivs = document.getElementsByTagName('div');
const genderRadios = document.getElementsByName('gender');
注意:
getElementsByClassName和getElementsByTagName返回的是动态集合,即文档变化时集合会自动更新;查询性能优于querySelectorAll,但遍历时需注意死循环。
2.2 现代选择方法(灵活、功能强大)
使用 CSS 选择器语法,几乎可以匹配任何元素。
| 方法 | 描述 | 返回值 |
|---|---|---|
document.querySelector(cssSelector) |
匹配第一个满足选择器的元素 | 单个元素或 null |
document.querySelectorAll(cssSelector) |
匹配所有满足选择器的元素 | 静态 NodeList |
示例:
// 选择第一个类为 .container 的元素
const container = document.querySelector('.container');
// 选择所有带有 data-type="special" 的 li 元素
const specialItems = document.querySelectorAll('li[data-type="special"]');
// 复杂选择器:选择 body 下所有偶数行的 div
const evenDivs = document.querySelectorAll('body > div:nth-child(even)');
静态 vs 动态:
querySelectorAll返回的是静态 NodeList,不会随文档改变而更新。- NodeList 具备
forEach方法,但并非数组,如需使用 map/filter 等,可用Array.from()或扩展运算符转换。
2.3 基于现有元素的范围选择
不仅可以用 document 调用选择器,任何元素实例也可以调用,从而在其后代中查找。
const card = document.querySelector('.card');
const title = card.querySelector('.title'); // 只在 card 内部查找
3. 事件:响应用户交互
事件是用户与页面交互的通道。事件驱动编程包含:获取元素、绑定事件监听器、编写处理函数。
3.1 事件绑定方式
推荐使用 addEventListener,因为它支持多个监听器、精确控制事件阶段。
element.addEventListener(eventType, handler [, options]);
示例:
const btn = document.getElementById('submitBtn');
btn.addEventListener('click', function(event) {
console.log('按钮被点击', event.target);
// 阻止默认行为,如表单提交
event.preventDefault();
});
3.2 常用事件类型
| 分类 | 事件名 | 触发时机 |
|---|---|---|
| 鼠标事件 | click |
鼠标单击 |
dblclick |
鼠标双击 | |
mouseenter |
鼠标进入元素(不冒泡) | |
mouseleave |
鼠标离开元素(不冒泡) | |
| 键盘事件 | keydown |
键盘按下 |
keyup |
键盘松开 | |
| 表单事件 | submit |
表单提交 |
input |
表单输入值变化(实时) | |
change |
表单值改变并失去焦点时 | |
| 焦点事件 | focus |
元素获得焦点 |
blur |
元素失去焦点 | |
| 文档/窗口 | DOMContentLoaded |
HTML 解析完成,DOM 就绪 |
load |
页面所有资源加载完成 | |
scroll |
滚动条滚动 | |
resize |
窗口大小改变 |
3.3 事件对象
事件处理函数自动接收一个 event 对象,包含丰富信息:
event.target:触发事件的实际元素(事件源)event.currentTarget:绑定监听器的元素(即this,箭头函数不能用)event.type:事件类型event.preventDefault():取消默认行为(链接跳转、表单提交等)event.stopPropagation():阻止事件冒泡- 键盘事件的
event.key、event.code - 鼠标事件的
event.clientX、event.clientY
3.4 事件委托(提高性能,处理动态元素)
利用事件冒泡机制,将事件绑定在父元素上,通过 event.target 判断实际触发子元素。适用于列表项、动态插入的元素等场景。
const todoList = document.querySelector('#todo-list');
todoList.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.classList.toggle('completed');
}
});
3.5 移除事件监听
必须使用与绑定相同的函数引用,匿名函数无法直接移除。
function handleClick(e) { /* ... */ }
element.addEventListener('click', handleClick);
// 稍后移除
element.removeEventListener('click', handleClick);
4. 节点修改:动态改变页面内容与结构
4.1 修改元素内容
| 属性 / 方法 | 描述 |
|---|---|
element.textContent |
获取/设置纯文本内容,自动转义 HTML 标签 |
element.innerText |
与 textContent 类似,但考虑 CSS 样式(隐藏元素不可见),且触发回流 |
element.innerHTML |
获取/设置 HTML 内容,会解析标签,存在 XSS 风险 |
最佳实践:
优先使用 textContent 处理文本,避免注入攻击;仅在需要插入 HTML 片段时使用 innerHTML,并确保内容可信。
const para = document.querySelector('p');
para.textContent = '这是安全的文本';
para.innerHTML = '<strong>加粗文本</strong>'; // 仅在内容可控时使用
4.2 修改元素属性
标准 HTML 属性可直接通过属性名操作,自定义属性(data-*)推荐使用 dataset。
- 标准属性:
element.id、element.href、element.src、element.className等。 - 通用方法:
element.getAttribute('attr')element.setAttribute('attr', 'value')element.removeAttribute('attr')
- 类名操作(推荐使用 classList):
element.classList.add('class')element.classList.remove('class')element.classList.toggle('class')element.classList.contains('class')(返回布尔值)
- data 属性:
<div data-user-id="123" data-role="admin"></div>const div = document.querySelector('div'); console.log(div.dataset.userId); // "123" div.dataset.isActive = 'true'; // 对应 data-is-active
4.3 修改样式
通过 element.style 可以修改内联样式,但更优雅的方式是操作类名(切换 CSS 类)。
element.style.color = 'red';
element.style.fontSize = '18px'; // 驼峰命名
// 更推荐:定义 CSS 类,然后切换
element.classList.toggle('highlight');
获取计算后的样式(包括继承与样式表值)使用 getComputedStyle。
const computedColor = window.getComputedStyle(element).color;
4.4 创建与插入节点
| 方法 | 描述 |
|---|---|
document.createElement(tag) |
创建元素节点 |
document.createTextNode(text) |
创建文本节点 |
parent.appendChild(newNode) |
将节点添加为父元素的最后一个子节点 |
parent.insertBefore(newNode, refNode) |
在父元素的指定子节点之前插入新节点 |
parent.append(node1, node2, ...) |
现代方法,可插入多个节点或文本字符串 |
parent.prepend(node1, ...) |
插入为第一个子节点 |
refNode.before(node) |
在某个节点前面插入 |
refNode.after(node) |
在某个节点后面插入 |
示例:创建完整列表项并插入
const ul = document.querySelector('ul');
// 创建元素
const li = document.createElement('li');
li.textContent = '新项目';
li.classList.add('new-item');
// 插入到列表末尾
ul.append(li);
// 或插入到第一个位置
ul.prepend(li);
4.5 替换与删除节点
// 替换
const oldNode = document.getElementById('old');
const newNode = document.createElement('span');
newNode.textContent = '新内容';
oldNode.parentNode.replaceChild(newNode, oldNode);
// 现代方法:直接调用 replaceWith
oldNode.replaceWith(newNode);
// 删除
const element = document.querySelector('.remove-me');
element.parentNode.removeChild(element);
// 现代方法:直接删除自身
element.remove();
4.6 克隆节点
node.cloneNode(deep):deep 为 true 时深克隆(包含后代节点),false 时仅克隆当前节点。
const template = document.querySelector('.template');
const clone = template.cloneNode(true);
document.body.appendChild(clone);
5. 实战模式:一个完整的待办事项示例
下面综合运用选择器、事件委托和 DOM 修改,实现基础待办添加与删除功能。
HTML 结构:
<div id="app">
<input type="text" id="todoInput" placeholder="输入新任务">
<button id="addBtn">添加</button>
<ul id="todoList"></ul>
</div>
JavaScript:
const input = document.getElementById('todoInput');
const addBtn = document.getElementById('addBtn');
const list = document.getElementById('todoList');
// 添加任务
addBtn.addEventListener('click', () => {
const text = input.value.trim();
if (text === '') return;
const li = document.createElement('li');
li.textContent = text;
// 添加删除按钮
const delBtn = document.createElement('button');
delBtn.textContent = '删除';
delBtn.classList.add('delete-btn');
li.appendChild(delBtn);
list.appendChild(li);
input.value = '';
input.focus();
});
// 事件委托处理列表点击(完成切换、删除)
list.addEventListener('click', (e) => {
const target = e.target;
if (target.tagName === 'LI') {
target.classList.toggle('completed');
}
if (target.classList.contains('delete-btn')) {
target.parentElement.remove();
}
});
6. 性能与最佳实践
- 减少回流与重绘:批量修改 DOM 时,使用
DocumentFragment或先移除元素修改后再插入,或使用cloneNode在内存中构建。 - 事件委托优先:在父容器上绑定一个监听器,避免为每个子元素单独绑定。
- 避免使用
innerHTML进行频繁拼接:使用createElement+append更具可预测性且安全。 - 缓存选择器结果:多次使用的元素应存储为变量,避免重复查询 DOM。
- 使用
requestAnimationFrame进行视觉更新:连续样式变化应放在动画帧回调中。
7. 总结
JavaScript DOM 操作是前端开发的基础。掌握现代选择器(querySelector/querySelectorAll)能快速定位元素;理解事件模型、学会使用事件委托可以提高代码健壮性;通过节点创建、属性/内容修改、插入删除等方法可以实现任意动态交互效果。遵循安全与性能实践,你将能构建出流畅、可维护的用户界面。
下一步建议:深入学习 DOM 遍历(父子兄弟节点)、MutationObserver 监听变化、以及结合 AJAX 动态获取数据更新 DOM。持续练习,将理论转化为肌肉记忆。