JS DOM操作与事件处理
2025/9/17大约 9 分钟
JavaScript DOM操作与事件处理
学习目标
- 理解DOM的基本概念和结构
- 掌握DOM元素的查找和操作方法
- 学会处理各种用户交互事件
- 了解事件委托和性能优化技巧
DOM基础概念
什么是DOM
DOM(Document Object Model,文档对象模型)是HTML和XML文档的编程接口。它将文档表示为一个树形结构,每个节点都是一个对象。
<!DOCTYPE html>
<html>
<head>
<title>DOM示例</title>
</head>
<body>
<div id="container">
<h1>标题</h1>
<p class="content">段落内容</p>
<ul>
<li>列表项1</li>
<li>列表项2</li>
</ul>
</div>
</body>
</html>
DOM节点类型
// 常见的节点类型
console.log(Node.ELEMENT_NODE); // 1 - 元素节点
console.log(Node.TEXT_NODE); // 3 - 文本节点
console.log(Node.COMMENT_NODE); // 8 - 注释节点
console.log(Node.DOCUMENT_NODE); // 9 - 文档节点
// 检查节点类型
let element = document.getElementById('container');
console.log(element.nodeType); // 1
console.log(element.nodeName); // "DIV"
console.log(element.nodeValue); // null(元素节点的nodeValue为null)
DOM元素查找
基本查找方法
// 通过ID查找(返回单个元素)
let container = document.getElementById('container');
console.log(container);
// 通过类名查找(返回HTMLCollection)
let contentElements = document.getElementsByClassName('content');
console.log(contentElements[0]);
// 通过标签名查找(返回HTMLCollection)
let paragraphs = document.getElementsByTagName('p');
console.log(paragraphs);
// 通过name属性查找(主要用于表单元素)
let inputs = document.getElementsByName('username');
console.log(inputs);
现代查找方法
// querySelector - 返回第一个匹配的元素
let firstParagraph = document.querySelector('p');
let containerById = document.querySelector('#container');
let contentByClass = document.querySelector('.content');
let firstListItem = document.querySelector('ul li');
console.log(firstParagraph);
console.log(containerById);
// querySelectorAll - 返回所有匹配的元素(NodeList)
let allParagraphs = document.querySelectorAll('p');
let allListItems = document.querySelectorAll('li');
let multipleClasses = document.querySelectorAll('.content, .highlight');
console.log(allParagraphs);
console.log(allListItems);
// 复杂选择器
let specificElement = document.querySelector('div#container > p.content');
let evenListItems = document.querySelectorAll('li:nth-child(even)');
元素关系导航
let element = document.querySelector('#container');
// 父元素
console.log(element.parentNode); // 父节点
console.log(element.parentElement); // 父元素
// 子元素
console.log(element.childNodes); // 所有子节点(包括文本节点)
console.log(element.children); // 所有子元素
console.log(element.firstChild); // 第一个子节点
console.log(element.firstElementChild); // 第一个子元素
console.log(element.lastChild); // 最后一个子节点
console.log(element.lastElementChild); // 最后一个子元素
// 兄弟元素
console.log(element.nextSibling); // 下一个兄弟节点
console.log(element.nextElementSibling); // 下一个兄弟元素
console.log(element.previousSibling); // 上一个兄弟节点
console.log(element.previousElementSibling); // 上一个兄弟元素
DOM元素操作
内容操作
let element = document.querySelector('#container');
// 文本内容
console.log(element.textContent); // 获取纯文本内容
element.textContent = '新的文本内容'; // 设置文本内容
// HTML内容
console.log(element.innerHTML); // 获取HTML内容
element.innerHTML = '<p>新的<strong>HTML</strong>内容</p>';
// 外部HTML
console.log(element.outerHTML); // 包含元素本身的HTML
// 安全的文本设置(防止XSS攻击)
let userInput = '<script>alert("XSS")</script>';
element.textContent = userInput; // 安全:会被当作纯文本
// element.innerHTML = userInput; // 危险:会执行脚本
属性操作
let img = document.querySelector('img');
// 标准属性操作
console.log(img.src); // 获取src属性
img.src = 'new-image.jpg'; // 设置src属性
img.alt = '新的图片描述'; // 设置alt属性
// 通用属性方法
console.log(img.getAttribute('src')); // 获取属性
img.setAttribute('data-id', '123'); // 设置属性
console.log(img.hasAttribute('alt')); // 检查属性是否存在
img.removeAttribute('title'); // 删除属性
// 数据属性(data-*)
img.dataset.userId = '456'; // 设置data-user-id
console.log(img.dataset.userId); // 获取data-user-id
// 布尔属性
let checkbox = document.querySelector('input[type="checkbox"]');
checkbox.checked = true; // 设置选中状态
checkbox.disabled = false; // 设置禁用状态
样式操作
let element = document.querySelector('#container');
// 内联样式
element.style.color = 'red';
element.style.fontSize = '16px';
element.style.backgroundColor = '#f0f0f0';
// 批量设置样式
Object.assign(element.style, {
width: '300px',
height: '200px',
border: '1px solid #ccc',
borderRadius: '5px'
});
// CSS类操作
element.className = 'container active'; // 设置类名
element.classList.add('highlight'); // 添加类
element.classList.remove('old-class'); // 删除类
element.classList.toggle('visible'); // 切换类
console.log(element.classList.contains('active')); // 检查类是否存在
// 获取计算后的样式
let computedStyle = window.getComputedStyle(element);
console.log(computedStyle.color); // 获取实际颜色值
console.log(computedStyle.fontSize); // 获取实际字体大小
元素创建和插入
点击展开示例
// 创建元素
let newDiv = document.createElement('div');
newDiv.textContent = '这是新创建的div';
newDiv.className = 'new-element';
// 创建文本节点
let textNode = document.createTextNode('纯文本节点');
// 插入元素
let container = document.querySelector('#container');
// 在末尾添加
container.appendChild(newDiv);
// 在指定位置插入
let referenceElement = container.querySelector('p');
container.insertBefore(newDiv, referenceElement);
// 现代插入方法
container.prepend(newDiv); // 在开头插入
container.append(newDiv); // 在末尾插入
referenceElement.before(newDiv); // 在元素前插入
referenceElement.after(newDiv); // 在元素后插入
// 替换元素
let oldElement = container.querySelector('.old');
let newElement = document.createElement('span');
newElement.textContent = '替换后的元素';
container.replaceChild(newElement, oldElement);
// 删除元素
container.removeChild(oldElement); // 传统方法
oldElement.remove(); // 现代方法
事件处理
事件监听器
点击展开示例
// 添加事件监听器
let button = document.querySelector('#myButton');
// 方法1:HTML属性(不推荐)
// <button onclick="handleClick()">点击</button>
// 方法2:DOM属性
button.onclick = function() {
console.log('按钮被点击了');
};
// 方法3:addEventListener(推荐)
button.addEventListener('click', function(event) {
console.log('按钮被点击了');
console.log('事件对象:', event);
});
// 箭头函数
button.addEventListener('click', (event) => {
console.log('使用箭头函数处理点击事件');
});
// 命名函数
function handleButtonClick(event) {
console.log('命名函数处理点击事件');
}
button.addEventListener('click', handleButtonClick);
// 移除事件监听器
button.removeEventListener('click', handleButtonClick);
事件对象
function handleEvent(event) {
// 事件基本信息
console.log('事件类型:', event.type); // 'click'
console.log('目标元素:', event.target); // 触发事件的元素
console.log('当前元素:', event.currentTarget); // 绑定事件的元素
// 鼠标事件信息
if (event.type === 'click') {
console.log('鼠标坐标:', event.clientX, event.clientY);
console.log('页面坐标:', event.pageX, event.pageY);
console.log('屏幕坐标:', event.screenX, event.screenY);
console.log('按键信息:', {
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey
});
}
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
}
document.addEventListener('click', handleEvent);
常用事件类型
点击展开示例
// 鼠标事件
element.addEventListener('click', handleClick); // 点击
element.addEventListener('dblclick', handleDoubleClick); // 双击
element.addEventListener('mousedown', handleMouseDown); // 鼠标按下
element.addEventListener('mouseup', handleMouseUp); // 鼠标释放
element.addEventListener('mouseover', handleMouseOver); // 鼠标悬停
element.addEventListener('mouseout', handleMouseOut); // 鼠标离开
element.addEventListener('mousemove', handleMouseMove); // 鼠标移动
// 键盘事件
document.addEventListener('keydown', handleKeyDown); // 按键按下
document.addEventListener('keyup', handleKeyUp); // 按键释放
document.addEventListener('keypress', handleKeyPress); // 按键按下(已废弃)
// 表单事件
let form = document.querySelector('form');
let input = document.querySelector('input');
form.addEventListener('submit', handleSubmit); // 表单提交
input.addEventListener('focus', handleFocus); // 获得焦点
input.addEventListener('blur', handleBlur); // 失去焦点
input.addEventListener('change', handleChange); // 值改变
input.addEventListener('input', handleInput); // 输入时触发
// 窗口事件
window.addEventListener('load', handleLoad); // 页面加载完成
window.addEventListener('resize', handleResize); // 窗口大小改变
window.addEventListener('scroll', handleScroll); // 页面滚动
// 事件处理函数示例
function handleKeyDown(event) {
console.log('按下的键:', event.key);
console.log('键码:', event.keyCode);
// 检测特殊按键组合
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Ctrl+S 被按下');
}
}
function handleSubmit(event) {
event.preventDefault(); // 阻止表单默认提交
let formData = new FormData(event.target);
console.log('表单数据:', Object.fromEntries(formData));
}
事件委托
// 事件委托:在父元素上监听子元素的事件
let list = document.querySelector('#todoList');
// 为所有列表项添加点击事件(包括动态添加的)
list.addEventListener('click', function(event) {
// 检查点击的是否是列表项
if (event.target.tagName === 'LI') {
console.log('点击了列表项:', event.target.textContent);
}
// 检查点击的是否是删除按钮
if (event.target.classList.contains('delete-btn')) {
let listItem = event.target.closest('li');
listItem.remove();
}
});
// 动态添加列表项
function addTodoItem(text) {
let li = document.createElement('li');
li.innerHTML = `
${text}
<button class="delete-btn">删除</button>
`;
list.appendChild(li);
}
// 添加新项目
addTodoItem('新的待办事项');
定时器
setTimeout和setInterval
点击展开示例
// setTimeout - 延迟执行
let timeoutId = setTimeout(function() {
console.log('3秒后执行');
}, 3000);
// 取消定时器
clearTimeout(timeoutId);
// setInterval - 重复执行
let intervalId = setInterval(function() {
console.log('每2秒执行一次');
}, 2000);
// 取消定时器
clearInterval(intervalId);
// 实用示例:倒计时
function countdown(seconds) {
let display = document.querySelector('#countdown');
let timer = setInterval(function() {
display.textContent = seconds;
seconds--;
if (seconds < 0) {
clearInterval(timer);
display.textContent = '时间到!';
}
}, 1000);
}
countdown(10); // 10秒倒计时
防抖和节流
点击展开示例
// 防抖:延迟执行,如果在延迟期间再次触发,则重新计时
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流:限制执行频率
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
let now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
// 使用示例
let searchInput = document.querySelector('#search');
// 防抖:用户停止输入500ms后才执行搜索
let debouncedSearch = debounce(function(event) {
console.log('执行搜索:', event.target.value);
}, 500);
searchInput.addEventListener('input', debouncedSearch);
// 节流:滚动事件最多每100ms执行一次
let throttledScroll = throttle(function() {
console.log('页面滚动位置:', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledScroll);
综合实例
动态待办事项列表
点击展开示例
class TodoApp {
constructor() {
this.todos = [];
this.nextId = 1;
this.init();
}
init() {
this.bindEvents();
this.render();
}
bindEvents() {
// 添加待办事项
document.querySelector('#addBtn').addEventListener('click', () => {
this.addTodo();
});
// 回车键添加
document.querySelector('#todoInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.addTodo();
}
});
// 事件委托处理列表操作
document.querySelector('#todoList').addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
let id = parseInt(e.target.dataset.id);
this.deleteTodo(id);
}
if (e.target.classList.contains('toggle-btn')) {
let id = parseInt(e.target.dataset.id);
this.toggleTodo(id);
}
});
// 筛选按钮
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.setFilter(e.target.dataset.filter);
});
});
}
addTodo() {
let input = document.querySelector('#todoInput');
let text = input.value.trim();
if (text) {
this.todos.push({
id: this.nextId++,
text: text,
completed: false,
createdAt: new Date()
});
input.value = '';
this.render();
}
}
deleteTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
this.render();
}
toggleTodo(id) {
let todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
}
}
setFilter(filter) {
this.currentFilter = filter;
this.render();
// 更新按钮状态
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === filter);
});
}
getFilteredTodos() {
switch (this.currentFilter) {
case 'active':
return this.todos.filter(todo => !todo.completed);
case 'completed':
return this.todos.filter(todo => todo.completed);
default:
return this.todos;
}
}
render() {
let list = document.querySelector('#todoList');
let filteredTodos = this.getFilteredTodos();
list.innerHTML = filteredTodos.map(todo => `
<li class="todo-item ${todo.completed ? 'completed' : ''}">
<span class="todo-text">${todo.text}</span>
<div class="todo-actions">
<button class="toggle-btn" data-id="${todo.id}">
${todo.completed ? '撤销' : '完成'}
</button>
<button class="delete-btn" data-id="${todo.id}">删除</button>
</div>
</li>
`).join('');
// 更新统计信息
this.updateStats();
}
updateStats() {
let total = this.todos.length;
let completed = this.todos.filter(todo => todo.completed).length;
let active = total - completed;
document.querySelector('#stats').textContent =
`总计: ${total}, 已完成: ${completed}, 待完成: ${active}`;
}
}
// 初始化应用
let app = new TodoApp();
最佳实践
性能优化
- 事件委托:减少事件监听器数量
- 防抖节流:控制事件触发频率
- 批量DOM操作:减少重排重绘
- 缓存DOM查询:避免重复查询
// 好的做法:缓存DOM查询
let container = document.querySelector('#container');
let items = container.querySelectorAll('.item');
// 批量操作
let fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
let div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment);
安全考虑
// 防止XSS攻击
function safeSetHTML(element, html) {
// 使用textContent而不是innerHTML
element.textContent = html;
}
// 或者使用专门的库进行HTML清理
// element.innerHTML = DOMPurify.sanitize(html);
本章小结
通过本章学习,你应该掌握了:
- ✅ DOM的基本概念和节点类型
- ✅ 各种DOM元素查找方法
- ✅ DOM元素的内容、属性、样式操作
- ✅ 元素的创建、插入、删除方法
- ✅ 事件监听器的添加和移除
- ✅ 事件对象的使用和事件委托
- ✅ 定时器和防抖节流技术
- ✅ DOM操作的最佳实践和性能优化
DOM操作是前端开发的核心技能,熟练掌握这些知识将为你构建交互式网页打下坚实基础。
下一步
接下来我们将学习JavaScript的高级特性和ES6+语法,包括闭包、箭头函数、解构赋值等现代JavaScript特性。