个人主页:Guiat
归属专栏:HTML CSS JavaScript
文章目录
正文
1. DOM 基础概念
DOM(文档对象模型)是 HTML 和 XML 文档的编程接口,它将网页表示为节点和对象的树形结构。
1.1 DOM 树结构
DOM 树由不同类型的节点组成:
- 文档节点(Document):整个文档的根节点
- 元素节点(Element):HTML 元素
- 文本节点(Text):元素内的文本
- 属性节点(Attribute):元素的属性
- 注释节点(Comment):HTML 注释
<!DOCTYPE html>
<html>
<head>
<title>DOM树示例</title>
</head>
<body>
<!-- 这是一个注释 -->
<div id="container" class="wrapper">
<h1>DOM操作</h1>
<p>这是一个段落</p>
</div>
</body>
</html>
1.2 获取 DOM 元素
JavaScript 提供了多种方法来获取 DOM 元素。
// 通过 ID 获取元素
const container = document.getElementById('container');
// 通过类名获取元素列表
const wrappers = document.getElementsByClassName('wrapper');
// 通过标签名获取元素列表
const paragraphs = document.getElementsByTagName('p');
// 使用 CSS 选择器获取第一个匹配元素
const heading = document.querySelector('h1');
// 使用 CSS 选择器获取所有匹配元素
const divs = document.querySelectorAll('div');
2. 操作 DOM 元素
2.1 创建和添加元素
// 创建新元素
const newParagraph = document.createElement('p');
// 创建文本节点
const text = document.createTextNode('这是动态创建的段落。');
// 将文本添加到新元素
newParagraph.appendChild(text);
// 将新元素添加到 DOM 树
container.appendChild(newParagraph);
// 插入到特定位置
const firstParagraph = document.querySelector('p');
container.insertBefore(newParagraph, firstParagraph);
// 使用 innerHTML 添加内容
container.innerHTML += '<p>使用 innerHTML 添加的段落</p>';
2.2 修改元素
// 修改元素内容
heading.textContent = '更新后的标题';
paragraph.innerHTML = '包含<strong>HTML</strong>的段落';
// 修改元素属性
const link = document.querySelector('a');
link.href = 'https://example.com';
link.setAttribute('target', '_blank');
// 获取属性值
const linkHref = link.getAttribute('href');
const hasTarget = link.hasAttribute('target');
// 移除属性
link.removeAttribute('target');
2.3 操作样式
// 直接修改样式
heading.style.color = 'blue';
heading.style.fontSize = '24px';
heading.style.margin = '20px 0';
// 添加、移除和切换类
container.classList.add('active');
container.classList.remove('wrapper');
container.classList.toggle('visible');
container.classList.replace('old-class', 'new-class');
// 检查是否包含类
const hasClass = container.classList.contains('active');
2.4 删除和替换元素
// 删除元素
paragraph.remove();
// 通过父元素删除子元素
container.removeChild(heading);
// 替换元素
const newHeading = document.createElement('h2');
newHeading.textContent = '新标题';
container.replaceChild(newHeading, heading);
3. DOM 事件
3.1 事件处理基础
// 方法一:使用属性
button.onclick = function() {
alert('按钮被点击了!');
};
// 方法二:addEventListener
button.addEventListener('click', function() {
alert('按钮被点击了!');
});
// 命名函数作为事件处理程序
function handleClick() {
alert('按钮被点击了!');
}
button.addEventListener('click', handleClick);
// 移除事件监听器
button.removeEventListener('click', handleClick);
3.2 事件对象
button.addEventListener('click', function(event) {
// 事件对象包含事件的详细信息
console.log(event.type); // "click"
console.log(event.target); // 触发事件的元素
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
});
3.3 事件传播
事件传播分为三个阶段:捕获阶段、目标阶段和冒泡阶段。
// 默认在冒泡阶段处理事件(第三个参数为 false 或省略)
parent.addEventListener('click', function() {
console.log('父元素被点击');
});
// 在捕获阶段处理事件(第三个参数为 true)
parent.addEventListener('click', function() {
console.log('捕获阶段:父元素');
}, true);
child.addEventListener('click', function(event) {
console.log('子元素被点击');
event.stopPropagation(); // 阻止事件继续传播
});
3.4 常见事件类型
3.4.1 鼠标事件
element.addEventListener('click', handleClick);
element.addEventListener('dblclick', handleDoubleClick);
element.addEventListener('mousedown', handleMouseDown);
element.addEventListener('mouseup', handleMouseUp);
element.addEventListener('mousemove', handleMouseMove);
element.addEventListener('mouseover', handleMouseOver);
element.addEventListener('mouseout', handleMouseOut);
element.addEventListener('mouseenter', handleMouseEnter);
element.addEventListener('mouseleave', handleMouseLeave);
3.4.2 键盘事件
document.addEventListener('keydown', function(event) {
console.log(`按键 ${event.key} 被按下`);
if (event.key === 'Enter') {
// 执行提交操作
}
// 检查修饰键
if (event.ctrlKey && event.key === 'c') {
console.log('按下了 Ctrl+C');
}
});
document.addEventListener('keyup', handleKeyUp);
document.addEventListener('keypress', handleKeyPress);
3.4.3 表单事件
const form = document.querySelector('form');
const input = document.querySelector('input');
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交
console.log('表单提交');
});
input.addEventListener('focus', function() {
console.log('输入框获得焦点');
});
input.addEventListener('blur', function() {
console.log('输入框失去焦点');
});
input.addEventListener('change', function() {
console.log('输入值已改变');
});
input.addEventListener('input', function() {
console.log('正在输入:', this.value);
});
4. 事件委托
事件委托是一种利用事件冒泡的机制,将事件监听器添加到父元素而不是每个子元素的技术。
// 不使用事件委托(低效)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', function() {
console.log('Item clicked:', this.textContent);
});
});
// 使用事件委托(高效)
const list = document.querySelector('.list');
list.addEventListener('click', function(event) {
if (event.target.classList.contains('item')) {
console.log('Item clicked:', event.target.textContent);
}
});
4.1 事件委托优势
- 减少内存占用,提高性能
- 无需为动态添加的元素绑定事件
- 代码更加简洁
// HTML 结构
// <ul id="todo-list">
// <li>任务1 <button class="delete">删除</button></li>
// <li>任务2 <button class="delete">删除</button></li>
// </ul>
const todoList = document.getElementById('todo-list');
todoList.addEventListener('click', function(event) {
// 检查是否点击了删除按钮
if (event.target.classList.contains('delete')) {
// 获取并删除父元素 (li)
const li = event.target.parentElement;
li.remove();
}
// 检查是否点击了任务本身
if (event.target.tagName === 'LI') {
event.target.classList.toggle('completed');
}
});
// 动态添加新任务
function addTask(taskText) {
const li = document.createElement('li');
li.textContent = taskText + ' ';
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.className = 'delete';
li.appendChild(deleteBtn);
todoList.appendChild(li);
}
5. DOM 操作性能优化
5.1 批量操作优化
// 低效方式:频繁操作 DOM
for (let i = 0; i < 1000; i++) {
const para = document.createElement('p');
para.textContent = `段落 ${i}`;
container.appendChild(para); // 每次循环都会重排
}
// 高效方式:文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const para = document.createElement('p');
para.textContent = `段落 ${i}`;
fragment.appendChild(para);
}
container.appendChild(fragment); // 只重排一次
5.2 减少重排和重绘
// 不好的做法:多次单独修改样式
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';
// 好的做法:使用 CSS 类一次性修改多个样式
element.classList.add('new-size');
// 或使用 cssText 属性
element.style.cssText = 'width: 100px; height: 200px; margin: 10px;';
// 在修改多个元素之前,先使用 display: none 隐藏元素
element.style.display = 'none';
// 进行多次 DOM 操作
element.style.display = 'block';
6. 实用 DOM 操作示例
6.1 图片滑块实现
const slider = document.querySelector('.slider');
const slides = document.querySelectorAll('.slide');
const prevBtn = document.querySelector('.prev');
const nextBtn = document.querySelector('.next');
let currentIndex = 0;
function showSlide(index) {
// 处理索引范围
if (index < 0) index = slides.length - 1;
if (index >= slides.length) index = 0;
// 更新当前索引
currentIndex = index;
// 移动滑块
const offset = -currentIndex * 100;
slider.style.transform = `translateX(${offset}%)`;
}
// 事件监听
nextBtn.addEventListener('click', () => {
showSlide(currentIndex + 1);
});
prevBtn.addEventListener('click', () => {
showSlide(currentIndex - 1);
});
// 自动播放
setInterval(() => {
showSlide(currentIndex + 1);
}, 3000);
6.2 拖放功能实现
const draggable = document.querySelector('.draggable');
let isDragging = false;
let offsetX, offsetY;
draggable.addEventListener('mousedown', (e) => {
isDragging = true;
// 计算鼠标在元素内的偏移量
offsetX = e.clientX - draggable.getBoundingClientRect().left;
offsetY = e.clientY - draggable.getBoundingClientRect().top;
// 设置样式
draggable.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
// 计算新位置
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
// 设置元素位置
draggable.style.left = `${x}px`;
draggable.style.top = `${y}px`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
draggable.style.cursor = 'grab';
});
6.3 动态表单验证
const form = document.getElementById('signup-form');
const username = document.getElementById('username');
const email = document.getElementById('email');
const password = document.getElementById('password');
// 显示错误信息
function showError(input, message) {
const formControl = input.parentElement;
formControl.className = 'form-control error';
const small = formControl.querySelector('small');
small.textContent = message;
}
// 显示成功状态
function showSuccess(input) {
const formControl = input.parentElement;
formControl.className = 'form-control success';
}
// 检查邮箱格式
function checkEmail(input) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(input.value.trim())) {
showSuccess(input);
} else {
showError(input, '邮箱格式不正确');
}
}
// 表单提交事件
form.addEventListener('submit', function(e) {
e.preventDefault();
// 检查用户名
if (username.value === '') {
showError(username, '用户名不能为空');
} else {
showSuccess(username);
}
// 检查邮箱
checkEmail(email);
// 检查密码
if (password.value === '') {
showError(password, '密码不能为空');
} else if (password.value.length < 6) {
showError(password, '密码长度至少为6个字符');
} else {
showSuccess(password);
}
});
结语
感谢您的阅读!期待您的一键三连!欢迎指正!