JavaScript 事件处理
学习 click/input/keydown 等事件监听 · 难度:进阶 · +15XP
JavaScript 事件处理 — 让网页响应用户操作
一、什么是事件?为什么它如此重要?
事件(Event)是浏览器在用户操作(或系统触发)时发出的信号。当用户点击按钮、按下键盘、移动鼠标、滚动页面、提交表单时,浏览器都会生成对应的事件对象。JavaScript 通过事件监听器(Event Listener)来捕获这些信号并执行相应代码。
为什么事件处理重要?没有事件,网页就失去了交互性。用户点击按钮没有反应,输入表单没有验证,页面就和一个静态 PDF 没有区别。事件处理是前端开发的核心技能,掌握它意味着你可以创建任何你想要的交互体验。
事件处理涉及三个核心概念:
- 事件目标(Event Target) — 哪个元素触发了事件
- 事件类型(Event Type) — 发生了什么(click、keydown、submit 等)
- 事件处理器(Event Handler) — 事件发生后执行的函数
二、事件绑定的三种方式 — 全面对比
| 方式 | 语法 | 优点 | 缺点 |
|---|---|---|---|
| HTML 属性 | <button onclick="handle()"> | 简单直观 | 耦合 HTML/JS,不便维护 |
| DOM 属性 | btn.onclick = handle; | JS 中直接赋值 | 只能绑定一个处理器 |
| addEventListener | btn.addEventListener("click", handle); | 可绑定多个、可移除、支持选项 | 语法稍长(推荐!) |
三、常用事件类型一览表
| 分类 | 事件名 | 触发时机 | 常用场景 |
|---|---|---|---|
| 鼠标事件 | click | 鼠标单击 | 按钮点击、链接点击 |
| dblclick | 鼠标双击 | 双击编辑 | |
| mouseover / mouseout | 鼠标移入/移出 | 悬停提示、菜单展开 | |
| 键盘事件 | keydown / keyup | 按键按下/松开 | 快捷键、游戏控制 |
| input | 输入框内容变化 | 实时搜索、字数统计 | |
| 表单事件 | submit | 表单提交 | 表单验证、AJAX 提交 |
| change | 选择或值改变 | 下拉菜单、复选框 | |
| 文档事件 | DOMContentLoaded | HTML 解析完成 | 初始化脚本 |
| scroll | 页面滚动 | 懒加载、回到顶部 | |
| 触摸事件 | touchstart | 手指触摸屏幕 | 移动端手势 |
| touchend | 手指离开屏幕 | 移动端点击 |
四、详细代码示例(逐行注释)
// ======== 1. addEventListener — 标准事件绑定 ========
const btn = document.querySelector("#my-btn");
// 参数1:事件类型,参数2:处理函数,参数3:选项(可选)
btn.addEventListener("click", function(event) {
console.log("按钮被点击了!");
console.log("事件对象:", event); // event 包含事件的所有信息
console.log("点击的目标元素:", event.target);
console.log("鼠标坐标:", event.clientX, event.clientY);
});
// ======== 2. 事件对象(Event Object)常用属性 ========
document.addEventListener("click", function(e) {
console.log(e.type); // 事件类型:"click"
console.log(e.target); // 触发事件的元素
console.log(e.currentTarget); // 绑定事件的元素(事件委托时和 target 不同)
console.log(e.clientX, e.clientY); // 鼠标相对视口的坐标
console.log(e.pageX, e.pageY); // 鼠标相对文档的坐标
// 键盘事件特有
// console.log(e.key); // 按下的键:"Enter"、"a"、"ArrowUp"
// console.log(e.code); // 物理键位:"KeyA"、"Space"
// console.log(e.ctrlKey); // 是否同时按了 Ctrl
});
// ======== 3. 移除事件监听 ========
function handleClick() {
console.log("只执行一次");
btn.removeEventListener("click", handleClick); // 移除监听
}
btn.addEventListener("click", handleClick);
// ======== 4. 事件委托(Event Delegation)— 性能优化关键 ========
// 不要在 1000 个 li 上分别绑定事件!而是绑定在父元素上
const list = document.querySelector("#my-list");
list.addEventListener("click", function(e) {
// 判断点击的是否是 li 元素
if (e.target.tagName === "LI") {
console.log("你点击了:", e.target.textContent);
e.target.classList.toggle("selected"); // 切换选中状态
}
// 更健壮的判断方式(匹配特定选择器)
if (e.target.matches(".delete-btn")) {
// 点击了删除按钮
e.target.closest("li").remove(); // 删除最近的 li 祖先
}
});
// 好处:后续动态添加的 li 也能自动响应点击,无需重新绑定!
// ======== 5. 阻止默认行为 + 阻止冒泡 ========
const form = document.querySelector("form");
form.addEventListener("submit", function(e) {
e.preventDefault(); // 阻止表单的默认提交行为(页面刷新)
// 改为 AJAX 提交
const formData = new FormData(form);
console.log("用 AJAX 提交数据,页面不刷新");
// fetch("/api/submit", { method: "POST", body: formData });
});
const inner = document.querySelector(".inner");
inner.addEventListener("click", function(e) {
e.stopPropagation(); // 阻止事件冒泡(父元素不会收到这个点击事件)
console.log("只触发 inner,不触发 outer");
});
// ======== 6. 事件传播三阶段 ========
// 捕获阶段(Capture)→ 目标阶段(Target)→ 冒泡阶段(Bubble)
const outer = document.querySelector(".outer");
outer.addEventListener("click", function() {
console.log("冒泡阶段触发(默认)");
});
outer.addEventListener("click", function() {
console.log("捕获阶段触发");
}, true); // 第三个参数 true = 捕获阶段监听
// ======== 7. 键盘事件 — 实现快捷键 ========
document.addEventListener("keydown", function(e) {
// Ctrl + S 保存快捷键
if (e.ctrlKey && e.key === "s") {
e.preventDefault(); // 阻止浏览器默认的保存网页行为
console.log("触发了自定义保存功能!");
}
// Esc 关闭弹窗
if (e.key === "Escape") {
document.querySelector(".modal").style.display = "none";
}
});
// ======== 8. 防抖(Debounce)— 减少高频事件触发 ========
// 搜索框输入时,等用户停止输入 500ms 后才发送请求
function debounce(fn, delay) {
let timer = null; // 闭包保存定时器 ID
return function(...args) { // 返回新函数
clearTimeout(timer); // 清除上一次的定时器
timer = setTimeout(() => { // 设置新的定时器
fn.apply(this, args); // delay 毫秒后执行
}, delay);
};
}
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input", debounce(function(e) {
console.log("发送搜索请求:", e.target.value);
// 实际项目中这里会调用 API 搜索
}, 500));
// ======== 9. 节流(Throttle)— 限制执行频率 ========
// 滚动事件每 200ms 最多触发一次
function throttle(fn, interval) {
let lastTime = 0; // 上次执行时间
return function(...args) {
const now = Date.now(); // 当前时间
if (now - lastTime >= interval) { // 超过间隔才执行
lastTime = now;
fn.apply(this, args);
}
};
}
window.addEventListener("scroll", throttle(function() {
console.log("滚动位置:", window.scrollY);
}, 200));
// ======== 10. once 选项 — 一次性事件 ========
btn.addEventListener("click", function() {
console.log("这个按钮点击一次后自动解绑");
}, { once: true }); // once 选项,触发一次后自动移除
五、事件冒泡 vs 捕获 — 一张图看懂
| 阶段 | 方向 | 顺序 | addEventListener 第三个参数 |
|---|---|---|---|
| 捕获阶段 | 从外向内 ↓ | window → document → html → body → ... → 目标元素 | true 或 {capture: true} |
| 目标阶段 | — | 到达目标元素本身 | 顺序按绑定先后 |
| 冒泡阶段 | 从内向外 ↑ | 目标元素 → ... → body → html → document → window | false(默认) |
六、实践任务
- 创建一个按钮,使用
addEventListener绑定点击事件,每次点击计数器 +1 并显示在页面上 - 创建一个输入框,使用防抖(debounce)实现:用户停止输入 1 秒后,在下方显示"搜索:xxx"
- 创建一个
<ul>列表,使用事件委托实现:点击任意<li>高亮显示,点击删除按钮删除该项 - 实现 Ctrl+Enter 快捷键提交表单
- 实现一个模态弹窗:点击"打开"显示弹窗,点击遮罩层(背景)或按 Esc 键关闭弹窗