交互是前端的灵魂,事件是交互的载体。理解"事件从哪来、往哪去、如何拦截",是写出健壮交互代码的分水岭。本文拆解事件传播的三个阶段,并给出实战中最高频的技巧——事件委托。

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. 常见事件速查

7. 性能与陷阱

💡 亲手做一次"事件传播可视化":给三层嵌套 div 各绑捕获和冒泡监听,点击最内层,把打印顺序记下来,你会永远记住三个阶段。