交互是前端的灵魂,事件是交互的载体。理解"事件从哪来、往哪去、如何拦截",是写出健壮交互代码的分水岭。本文拆解事件传播的三个阶段,并给出实战中最高频的技巧——事件委托。
1. 监听事件的基本姿势
const btn = document.querySelector("#btn");
// 推荐:addEventListener,可以绑定多个
btn.addEventListener("click", () => {
console.log("被点击了");
});
不推荐用 onclick 属性赋值——它只能挂一个监听,新赋值会覆盖旧的;移除监听需传入同一个函数引用。
2. 事件对象 event
document.querySelector("a").addEventListener("click", (e) => {
e.preventDefault(); // 阻止默认行为(比如跳转)
console.log(e.target); // 实际触发事件的元素
console.log(e.currentTarget); // 绑定监听的元素
console.log(e.type); // "click"
});
document.addEventListener("keydown", (e) => {
if (e.key === "Enter") console.log("按下了 Enter");
});
3. 事件传播的三个阶段
点击嵌套元素时,事件会经历三个阶段:① 捕获(从 window 向下到目标)→ ② 目标(命中元素)→ ③ 冒泡(从目标向上回 window)。默认监听器在冒泡阶段触发。
// 第三个参数传 true,在捕获阶段触发
const outer = document.querySelector("#outer");
outer.addEventListener("click", () => console.log("捕获"), true);
outer.addEventListener("click", () => console.log("冒泡"));
4. 阻止传播与默认行为
| 方法 | 作用 | 使用场景 |
|---|---|---|
| preventDefault() | 阻止浏览器默认行为,不阻断传播 | 阻止表单提交、链接跳转 |
| stopPropagation() | 阻止事件继续传播(捕获/冒泡) | 嵌套元素点击互不干扰 |
| stopImmediatePropagation() | 阻止传播,并阻止同元素其他监听器 | 紧急拦截,极少使用 |
5. 事件委托:一劳永逸的绑定方式
与其给每个 li 都绑监听,不如绑在父容器上,利用冒泡统一处理;动态新增的元素也能自动生效。
const list = document.querySelector("#list");
list.addEventListener("click", (e) => {
// 只处理点击 li 的情况
if (e.target.tagName === "LI") {
console.log("你点击了:", e.target.textContent);
}
});
// 动态添加的 li 也天然生效
const li = document.createElement("li");
li.textContent = "我是后来加的";
list.appendChild(li);
6. 常见事件速查
- 鼠标:click、dblclick、mouseenter、mouseleave(后两个不冒泡)
- 键盘:keydown、keyup,配合
e.key判断按键 - 表单:submit、input、change、focus、blur
- 滚动与尺寸:scroll、resize(注意节流/防抖)
7. 性能与陷阱
- 滚动/缩放事件别做重活:配合
requestAnimationFrame或防抖节流。 - 移除无用监听:SPA 中页面销毁时记得
removeEventListener,避免内存泄漏。 - 分清 target 与 currentTarget:委托场景里两者经常不同,用错会拿到意外元素。
💡 亲手做一次"事件传播可视化":给三层嵌套 div 各绑捕获和冒泡监听,点击最内层,把打印顺序记下来,你会永远记住三个阶段。