Day-11-事件处理

Day 11: 事件处理

前情回顾: 在 Day 10 中,我们学习了 DOM 操作基础,了解了如何选择、修改、创建和删除 DOM 元素。今天我们要学习的是如何让页面"活"起来——响应用户的操作。

🎯 学习目标

  • 理解什么是事件和事件流
  • 掌握事件监听器的添加和移除
  • 了解事件对象(Event Object)的属性和方法
  • 学会阻止默认行为和事件冒泡
  • 掌握常见的事件类型(鼠标、键盘、表单等)
  • 了解事件委托的原理和应用

💡 核心概念

什么是事件(Event)?

事件是文档或浏览器窗口中发生的特定交互瞬间。例如:

  • 用户点击按钮
  • 用户按下键盘
  • 页面加载完成
  • 表单提交

事件是 JavaScript 与用户交互的核心机制。

事件流

当一个事件发生时,事件会在 DOM 树中传播。有三个阶段

  1. 捕获阶段(Capture Phase): 从 window 向下传播到目标元素
  2. 目标阶段(Target Phase): 事件到达目标元素
  3. 冒泡阶段(Bubbling Phase): 从目标元素向上传播回 window
点击按钮时的流程:

捕获阶段:window → document → html → body → div → button
目标阶段:button
冒泡阶段:button → div → body → html → document → window

默认情况下,事件监听器在冒泡阶段触发。

📝 基本语法

添加事件监听器

使用 addEventListener() 方法添加事件监听器:

element.addEventListener(event, function, useCapture);

参数:

  • event: 事件名称(如 "click", "mouseover"
  • function: 事件处理函数
  • useCapture: 可选,是否在捕获阶段触发(默认 false

示例:

// 选择按钮
const button = document.querySelector('button');

// 添加点击事件
button.addEventListener('click', function(event) {
    console.log('按钮被点击了!');
});

移除事件监听器

使用 removeEventListener() 方法移除:

// 定义命名函数
function handleClick(event) {
    console.log('按钮被点击了!');
}

// 添加监听器
button.addEventListener('click', handleClick);

// 移除监听器
button.removeEventListener('click', handleClick);

注意: 只能移除命名函数,不能移除匿名函数!


🎮 示例代码

示例 1: 基本的点击事件

<!DOCTYPE html>
<html>
<head>
    <title>事件处理示例</title>
</head>
<body>
    <button id="myButton">点击我</button>
    <p id="message">等待点击...</p>

    <script>
        // 选择元素
        const button = document.getElementById('myButton');
        const message = document.getElementById('message');

        // 添加点击事件
        button.addEventListener('click', function() {
            message.textContent = '按钮被点击了!';
            message.style.color = 'green';
        });
    </script>
</body>
</html>

效果: 点击按钮后,文字会变为绿色并显示"按钮被点击了!"


示例 2: 事件对象(Event Object)

事件处理函数会接收一个事件对象,包含事件的详细信息:

button.addEventListener('click', function(event) {
    // event.type: 事件类型
    console.log('事件类型:', event.type); // "click"

    // event.target: 触发事件的元素
    console.log('目标元素:', event.target); // <button>元素

    // event.clientX, event.clientY: 鼠标位置
    console.log('鼠标位置:', event.clientX, event.clientY);

    // event.timeStamp: 事件发生的时间戳
    console.log('时间戳:', event.timeStamp);
});

示例 3: 阻止默认行为

有些元素有默认行为,例如:

  • 点击链接会跳转
  • 点击提交按钮会提交表单
  • 按空格键会向下滚动

使用 event.preventDefault() 可以阻止默认行为:

<a id="link" href="https://example.com">点击我不会跳转</a>

<script>
    const link = document.getElementById('link');

    link.addEventListener('click', function(event) {
        // 阻止链接跳转
        event.preventDefault();
        alert('链接被点击了,但没有跳转!');
    });
</script>

示例 4: 阻止事件冒泡

<div id="outer" style="padding: 50px; background: lightblue;">
    外层
    <div id="inner" style="padding: 30px; background: lightcoral;">
        内层
    </div>
</div>

<script>
    const outer = document.getElementById('outer');
    const inner = document.getElementById('inner');

    outer.addEventListener('click', function() {
        console.log('外层被点击');
    });

    inner.addEventListener('click', function(event) {
        console.log('内层被点击');

        // 阻止事件冒泡到外层
        event.stopPropagation();
    });
</script>

效果: 点击内层时,只会输出"内层被点击",不会触发外层的点击事件。


示例 5: 常见事件类型

鼠标事件

const box = document.getElementById('box');

// 点击
box.addEventListener('click', function() {
    console.log('点击');
});

// 双击
box.addEventListener('dblclick', function() {
    console.log('双击');
});

// 鼠标移入
box.addEventListener('mouseenter', function() {
    this.style.backgroundColor = 'lightblue';
});

// 鼠标移出
box.addEventListener('mouseleave', function() {
    this.style.backgroundColor = 'lightcoral';
});

// 鼠标移动
box.addEventListener('mousemove', function(event) {
    console.log(`鼠标位置: ${event.clientX}, ${event.clientY}`);
});

键盘事件

// 按下按键
document.addEventListener('keydown', function(event) {
    console.log(`按键: ${event.key}`);
    console.log(`键码: ${event.code}`);

    // 检测特定按键
    if (event.key === 'Enter') {
        console.log('回车键被按下');
    }

    // 检测组合键
    if (event.ctrlKey && event.key === 's') {
        event.preventDefault(); // 阻止保存
        console.log('Ctrl+S 被按下');
    }
});

// 释放按键
document.addEventListener('keyup', function(event) {
    console.log(`按键 ${event.key} 被释放`);
});

表单事件

<form id="myForm">
    <input type="text" id="username" placeholder="用户名">
    <input type="email" id="email" placeholder="邮箱">
    <button type="submit">提交</button>
</form>

<script>
    const form = document.getElementById('myForm');
    const username = document.getElementById('username');

    // 表单提交
    form.addEventListener('submit', function(event) {
        event.preventDefault(); // 阻止表单默认提交

        const name = username.value;
        console.log('提交的用户名:', name);
    });

    // 输入框内容变化
    username.addEventListener('input', function(event) {
        console.log('当前输入:', event.target.value);
    });

    // 获得焦点
    username.addEventListener('focus', function() {
        this.style.backgroundColor = 'lightyellow';
    });

    // 失去焦点
    username.addEventListener('blur', function() {
        this.style.backgroundColor = 'white';
        console.log('最终输入:', this.value);
    });
</script>

页面事件

// 页面加载完成
window.addEventListener('load', function() {
    console.log('页面所有资源(图片、样式等)已加载完成');
});

// DOM 加载完成(更快)
document.addEventListener('DOMContentLoaded', function() {
    console.log('DOM 已加载完成,可以操作元素了');
});

// 页面滚动
window.addEventListener('scroll', function() {
    console.log('滚动位置:', window.scrollY);
});

// 窗口大小改变
window.addEventListener('resize', function() {
    console.log('窗口大小:', window.innerWidth, window.innerHeight);
});

示例 6: 事件委托

事件委托是利用事件冒泡机制,将事件处理交给父元素处理。

问题: 如果有 100 个按钮,是否要添加 100 个监听器?

解决: 使用事件委托,只添加 1 个监听器!

<ul id="list">
    <li><button>按钮 1</button></li>
    <li><button>按钮 2</button></li>
    <li><button>按钮 3</button></li>
    <!-- 可能有更多按钮 -->
</ul>

<script>
    const list = document.getElementById('list');

    // 只添加一个监听器到父元素
    list.addEventListener('click', function(event) {
        // 检查点击的是否是按钮
        if (event.target.tagName === 'BUTTON') {
            console.log('点击了:', event.target.textContent);
        }
    });

    // 动态添加的按钮也会生效!
    const li = document.createElement('li');
    const button = document.createElement('button');
    button.textContent = '按钮 4';
    li.appendChild(button);
    list.appendChild(li);
</script>

事件委托的优势:

  • ✅ 减少事件监听器数量(提高性能)
  • ✅ 动态添加的元素自动绑定事件
  • ✅ 代码更简洁

✍️ 练习任务

练习 1: 点击计数器

目标: 创建一个计数器,每次点击按钮就加 1。

要求:

  1. 显示当前计数值(初始为 0)
  2. 点击"增加"按钮,计数 +1
  3. 点击"减少"按钮,计数 -1
  4. 点击"重置"按钮,计数归零

HTML 结构:

<h1>计数器: <span id="count">0</span></h1>
<button id="increase">增加</button>
<button id="decrease">减少</button>
<button id="reset">重置</button>

提示:

  • 使用 addEventListener 为每个按钮添加事件
  • 使用 textContent 更新计数值

练习 2: 输入验证

目标: 创建一个表单,实时验证用户输入。

需求:

  1. 用户名:至少 3 个字符
  2. 邮箱:必须包含 @
  3. 密码:至少 8 个字符
  4. 实时显示验证结果(✓ 或 ✗)

提示:

  • 使用 input 事件监听输入变化
  • 使用正则表达式验证邮箱格式

练习 3: 简易待办事项

目标: 使用事件委托实现待办事项列表。

功能:

  1. 输入框输入任务,按回车添加
  2. 点击任务可以切换完成状态
  3. 点击删除按钮可以删除任务
  4. 使用事件委托处理点击事件

提示:

  • 使用 keydown 事件检测回车键
  • 使用 event.target 判断点击的是哪个元素
  • 使用 element.classList.toggle() 切换样式

练习 4: 键盘快捷键

目标: 实现一个支持快捷键的简单应用。

快捷键:

  • Ctrl + B: 加粗选中文字
  • Ctrl + I: 斜体选中文字
  • Ctrl + U: 下划线选中文字
  • Escape: 清除格式

提示:

  • 使用 document.execCommand() 实现文本格式化
  • 使用 event.ctrlKey 检测 Ctrl 键

🎓 今日挑战

交互式画板

目标: 创建一个简单的画板应用。

功能需求:

  1. 画图功能:

    • 鼠标按下开始画图
    • 鼠标移动时绘制线条
    • 鼠标松开停止画图
  2. 工具栏:

    • 选择颜色
    • 选择画笔粗细
    • 清空画板
    • 保存图片
  3. 快捷键:

    • Ctrl + Z: 撤销
    • Ctrl + S: 保存
    • C: 清空

技术要点:

  • 使用 <canvas> 元素
  • 使用 mousedown, mousemove, mouseup 事件
  • 使用 canvas.getContext('2d') API
  • 使用 canvas.toDataURL() 保存图片

难度: ⭐⭐⭐⭐☆


💡 常见问题

Q: addEventListeneronclick 有什么区别?

A: 推荐使用 addEventListener,原因:

  1. 可以添加多个监听器:
// onclick 只能有一个
button.onclick = function() { /* ... */ };
button.onclick = function() { /* ... */ }; // 覆盖上面的

// addEventListener 可以有多个
button.addEventListener('click', function() { /* ... */ });
button.addEventListener('click', function() { /* ... */ }); // 两个都执行
  1. 可以控制捕获/冒泡阶段

  2. 可以移除监听器


Q: 什么时候使用事件委托?

A: 在以下情况使用事件委托:

  1. 有大量相似元素(如列表、表格)
  2. 动态添加元素(新元素自动绑定事件)
  3. 提高性能(减少监听器数量)

Q: event.targetevent.currentTarget 有什么区别?

A:

  • event.target: 触发事件的原始元素
  • event.currentTarget: 绑定事件的当前元素
outer.addEventListener('click', function(event) {
    console.log(event.target);      // 点击的元素(可能是 inner)
    console.log(event.currentTarget); // outer(绑定监听器的元素)
});

📌 关键要点

  • ✅ 使用 addEventListener() 添加事件监听器
  • ✅ 事件处理函数会接收一个事件对象event
  • event.preventDefault() 阻止默认行为
  • event.stopPropagation() 阻止事件冒泡
  • ✅ 常见事件类型:click, keydown, input, submit, load
  • ✅ 事件委托利用冒泡机制,在父元素处理子元素事件
  • ✅ 事件委托可以提高性能,适合动态元素

学习时间: 2026-03-11
难度: ⭐⭐⭐☆☆
预计用时: 2-3 小时
关键词: 事件, addEventListener, 事件对象, 事件委托
相关标签: #07-DOM操作


🎉 恭喜完成 Day 11! 你已经掌握了 JavaScript 事件处理的核心知识。下一课我们将学习表单处理和验证。