【JavaScript】DOM操作与事件

在这里插入图片描述

个人主页: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);
  }
});

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guiat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值