JavaScript-事件学习

深入理解 JavaScript 事件机制与解耦

在学习前端的过程中,事件机制是一个非常核心但又容易被忽略的概念。

刚接触时,我曾以为"事件"是 JavaScript 发明出来的东西。但随着理解的加深,我才发现:

  • 事件是客观存在的用户或系统行为(比如点击、键盘输入、页面加载)。
  • JavaScript 只是提供了对事件的描述和处理方式

这篇文章是我对事件机制的学习心得,既有理论总结,也包含了可以直接运行的小 demo。希望能帮你更直观地理解事件,以及如何利用事件机制让代码更加解耦。


什么是事件?

事件是用户与页面交互或系统触发的动作。

在 JavaScript 中,浏览器会为每次事件创建一个 事件对象 (Event) ,它是对事件的描述载体,包含:

  • 事件类型(click、keydown...)
  • 事件发生的位置
  • 当前所处的传播阶段(捕获/目标/冒泡)

而我们编写的事件处理程序,就是用来应对这些事件的逻辑。

👉 换句话说:事件是行为本身,事件对象是浏览器给我们的描述,事件处理程序是我们写的应对方案。


事件的传播流程

HTML 是层级结构,所以一个事件不会孤立存在。

当你点击一个按钮时,实际上也触发了它的父元素 <div>,甚至 <body><html>

浏览器规定了事件传播的三个阶段:

  1. 捕获阶段:事件从外向内传递(window → document → body ... → 目标元素)。
  2. 目标阶段:事件到达真正触发的元素。
  3. 冒泡阶段:事件从目标元素再逐层往外传递。

Demo:事件三阶段

xml 复制代码
<div id="outer">
  <button id="btn">点我</button>
</div>

<script>
  const outer = document.getElementById("outer");
  const btn = document.getElementById("btn");

  // 捕获阶段
  outer.addEventListener("click", () => {
    console.log("捕获阶段: outer");
  }, true);

  // 目标阶段
  btn.addEventListener("click", () => {
    console.log("目标阶段: button");
  });

  // 冒泡阶段
  outer.addEventListener("click", () => {
    console.log("冒泡阶段: outer");
  });
</script>

👉 点击按钮时,输出顺序是:

捕获阶段 → 目标阶段 → 冒泡阶段。


EventTarget 与事件对象

事件发生时,浏览器会把 事件对象 传递给监听器。

  • event.target:事件的源头(谁被点了)。
  • event.currentTarget:监听器绑定在哪个元素上。

Demo:target vs currentTarget

xml 复制代码
<ul id="list">
  <li>苹果</li>
  <li>香蕉</li>
  <li>橘子</li>
</ul>

<script>
  const ul = document.getElementById("list");
  ul.addEventListener("click", (event) => {
    console.log("target:", event.target);       // 实际触发的元素
    console.log("currentTarget:", event.currentTarget); // 绑定监听器的元素
  });
</script>

👉 点击"香蕉"时:

  • target<li>
  • currentTarget<ul>

自定义事件与派发

浏览器会自动派发常见的事件(click、input、load...)。

但如果是自定义的业务逻辑,我们可以用 CustomEvent + dispatchEvent() 来实现。

Demo:自定义登录事件

xml 复制代码
<button id="loginBtn">登录</button>

<script>
  const btn = document.getElementById("loginBtn");

  // 监听自定义事件
  btn.addEventListener("user:login", (event) => {
    console.log("执行登录逻辑:", event.detail);
  });

  // 点击按钮时派发自定义事件
  btn.addEventListener("click", () => {
    const loginEvent = new CustomEvent("user:login", {
      detail: { username: "张三" }
    });
    btn.dispatchEvent(loginEvent);
  });
</script>

👉 点击按钮时,会触发 user:login,并把数据传给监听器。


事件与解耦

假设我们有一个 login() 函数,需要在导航栏、文章页等多个地方更新 UI。

如果把所有逻辑都写在一个函数里,那么函数会越来越臃肿。

事件机制可以帮我们解耦,让代码更清晰。

❌ 未解耦:函数过于臃肿

xml 复制代码
<button id="navBtn">导航栏登录</button>
<button id="articleBtn">文章页登录</button>

<script>
  function login() {
    console.log("用户登录成功");

    // 导航栏更新
    document.getElementById("navBtn").textContent = "已登录";

    // 文章页面更新
    document.getElementById("articleBtn").textContent = "欢迎回来";
  }

  document.getElementById("navBtn").addEventListener("click", login);
  document.getElementById("articleBtn").addEventListener("click", login);
</script>

👉 如果以后再加"侧边栏登录提示",就得继续改 login(),维护成本很高。


✅ 使用事件解耦:login 只负责派发

xml 复制代码
<button id="navBtn">导航栏登录</button>
<button id="articleBtn">文章页登录</button>

<script>
  function login() {
    console.log("用户登录成功");

    // 只负责派发事件
    const loginEvent = new CustomEvent("user:login", {
      detail: { username: "张三" }
    });
    window.dispatchEvent(loginEvent);
  }

  // 公共触发点
  document.getElementById("navBtn").addEventListener("click", login);
  document.getElementById("articleBtn").addEventListener("click", login);

  // 导航栏监听
  window.addEventListener("user:login", (e) => {
    document.getElementById("navBtn").textContent = `欢迎 ${e.detail.username}`;
  });

  // 文章页面监听
  window.addEventListener("user:login", (e) => {
    document.getElementById("articleBtn").textContent = `Hi, ${e.detail.username}`;
  });
</script>

👉 好处:

  • login() 专注于派发事件,不再关心 UI。
  • 导航栏、文章页各自监听并处理自己的逻辑。
  • 未来新增功能时,只需要写新的监听器,不需要改 login()

这就是事件机制带来的解耦优势


总结

通过这篇文章,我们把 JavaScript 的事件机制串了一遍:

  • 事件是客观存在的行为,事件对象是它的描述。
  • DOM 事件遵循三个阶段:捕获 → 目标 → 冒泡。
  • target 表示事件源,currentTarget 表示监听器绑定的对象。
  • 自定义事件和事件派发机制,可以帮助我们实现更好的解耦和扩展性。

思考问题

  1. 如果我不使用自定义事件,能不能用"发布-订阅模式"实现类似的解耦?
  2. 在 React 或 Vue 中,事件机制是怎么封装的?和原生事件有何不同?
  3. 在什么情况下,使用事件冒泡会比在子元素单独绑定监听器更高效?

欢迎你在学习或工作中思考这些问题,也可以在评论区分享你的看法。


相关推荐
知识分享小能手2 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(下)——知识点详解 + 案例实战(5)
前端·javascript·学习·微信小程序·小程序·vue·前端开发
aidingni8884 小时前
掌握 TCJS 游戏摄像系统:打造动态影院级体验
前端·javascript
我是日安5 小时前
从零到一打造 Vue3 响应式系统 Day 23 - Watch:基础实现
前端·javascript·vue.js
FogLetter5 小时前
Map 与 WeakMap:内存管理的艺术与哲学
前端·javascript
前端伪大叔5 小时前
第15篇:Freqtrade策略不跑、跑错、跑飞?那可能是这几个参数没配好
前端·javascript·后端
課代表9 小时前
Acrobat DC 文本域表单验证中的 js 使用
javascript·正则表达式·表单验证·数据完整性·字段验证·事件对象·自定义验证
用户6387994773059 小时前
Next.js 多语言对决:next-intl vs next-i18next vs Intlayer
javascript
Keepreal4969 小时前
谈谈对javascript原型链的理解以及原型链的作用
前端·javascript
itslife9 小时前
vite 源码 - 配置
前端·javascript