惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Comments on: Blog
GbyAI
GbyAI
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
罗磊的独立博客
人人都是产品经理
人人都是产品经理
F
Full Disclosure
IT之家
IT之家
Know Your Adversary
Know Your Adversary
Engineering at Meta
Engineering at Meta
K
Kaspersky official blog
T
Tor Project blog
V
Visual Studio Blog
S
SegmentFault 最新的问题
P
Proofpoint News Feed
V
Vulnerabilities – Threatpost
T
True Tiger Recordings
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Hacker News
The Hacker News
MyScale Blog
MyScale Blog
Latest news
Latest news
Blog — PlanetScale
Blog — PlanetScale
Cyberwarzone
Cyberwarzone
Stack Overflow Blog
Stack Overflow Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Project Zero
Project Zero
AWS News Blog
AWS News Blog
Martin Fowler
Martin Fowler
F
Fox-IT International blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Scott Helme
Scott Helme
C
Check Point Blog
博客园 - Franky
P
Proofpoint News Feed
S
Securelist
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
U
Unit 42
P
Privacy & Cybersecurity Law Blog
NISL@THU
NISL@THU
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
D
Docker
Hacker News - Newest:
Hacker News - Newest: "LLM"
WordPress大学
WordPress大学
李成银的技术随笔
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
Schneier on Security
N
Netflix TechBlog - Medium

掘金

从“连接不上”到“交易成功”:我用 @solana/web3.js 在 React 中搞定 Solana 钱包交互的全过程 juejin.cn 海量人群包存储优化:基于 RoaringBitmap 交换格式与 Redis 分片 Bitmap 的实践 juejin.cn juejin.cn 鸿蒙项目首页启动链路与 ArkUI 架构学习总结 如何手写一个 AI Agent 工具调用循环(Tool Loop) Tauri 应用首次上架 App Store 被驳回了 3 次(iOS)和 12 轮(macOS)的经历 juejin.cn Flutter 桌面小组件开发 现代多模态大模型的核心基础:Unified Self-Attention juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn Transformer 原论文怎么训出来的:8 张 P100、12 小时、warmup 4000 步 Hermes 升级后,我的 Telegram 附件突然发不出来了 Transformer 中的前馈网络:那个看似平平无奇的两层 MLP,其实是「记忆」所在 AI Coding开始进入第四个时代,我还没上车呢! 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 juejin.cn Vibe Coding 全栈实战:章鱼哥解题 01|搭好产品底座与登录链路 juejin.cn 我让 AI 加了一个开关,结果代码走了原本不该走的分支 Manim物理模拟:别自己写欧拉了! 我也该升级了,陪伴了我7年的博客 juejin.cn juejin.cn MCP 高德地图实战:当 AI 学会使用工具,一个协议如何重塑大模型的行动边界 用魔法打败魔法:我让AI替我去面试前端岗,AI面试官给我打了92分,还发了offer juejin.cn juejin.cn juejin.cn juejin.cn Android Input Spy Window Claude Code CLI 命令大全:60 个原生命令一次讲清 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn 关于一个新手小白靠claude帮助下的全栈留言板项目开发 juejin.cn juejin.cn juejin.cn 从本地开发到生产部署:用 Docker Compose 跑通 NestJS、MySQL 与 Milvus AI应用开发七:可以替代 RAG 的技术 juejin.cn 小书匠:一款本地优先、去中心化的全能笔记软件 juejin.cn juejin.cn juejin.cn Shadow实战接入与生产落地:从零搭建到稳定运行 Shadow Transform:编译期的魔法——字节码替换实战 juejin.cn juejin.cn Hermes Agent:一个真正“会成长”的开源 AI Agent,正在改变 AI 自动化玩法 juejin.cn juejin.cn juejin.cn 残差连接:为什么深层网络必须留一条直路 juejin.cn FastAPI 从入门到实战:3 分钟构建高性能异步 API juejin.cn juejin.cn CryptoJS:数据安全的JavaScript加密利器 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn ArkClaw AI 盯盘管家 —— 从手动口令到自动推送,4 套预置定时任务模版一键启用 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn “杀!杀!杀!”、“我最讨厌事后道歉”——骂“杀哥”之前,谁还没当过情绪崩溃的人
5分钟搞懂事件委托
豹哥学前端 · 2026-05-15 · via 掘金

面试官问“事件委托”是什么?我用“班主任管全班”的例子让他直点头

动态添加的按钮无法绑定事件?写几十行 addEventListener 把自己写晕?
别怕,事件委托就是你的救星。今天我用最土的比喻 + 最全的代码,让你彻底搞懂它。


一、先看一段“翻车”代码

你做了一个待办清单,里面有个“删除”按钮,你正常绑定点击事件:

document.querySelector('.delete-btn').addEventListener('click', () => {
  console.log('删除');
});

一开始几个待办是写死的,删除正常。后来你通过 JS 动态添加新的待办,发现新待办的删除按钮完全没反应。

原因很简单:绑定事件的时候,那些新按钮根本还没出生。你给一个不存在的人打电话,当然打不通。


二、了解“事件冒泡”:就是教室里放了一个屁

事件冒泡是事件委托的基础。简单说:

你点击页面上的某个元素,这个点击事件会像气泡一样,从当前元素一直往上冒,直到最顶层的 document

举个例子:

<div id="grandpa">
  <div id="father">
    <button id="son">点我</button>
  </div>
</div>

点击按钮,事件触发顺序:
button#father#grandpabodyhtmldocument

就像教室里某个角落放了一个屁,臭味先被旁边同学闻到,然后传遍全班,最后班主任也闻到了。

关键:你可以选择在任何一个祖先元素上“闻”到这个事件。


三、什么是事件委托? —— 班主任管全班

既然事件会冒泡,那我不把监听器直接绑在按钮上,而是绑在按钮的父容器(甚至更高层)。等事件冒上来了,我再看看这个事件到底是谁触发的,然后执行对应的操作。

生活类比

  • 传统做法:你给班级里每个学生发一部手机,有事直接打你电话。学生毕业了,手机号注销,新来的学生还得再办手机。累死。
  • 事件委托:你只给班主任(父容器)配一部座机。所有学生打电话都先打给班主任,班主任再根据分机号(event.target)找到对应的学生。学生换了,分机号还在,永远不用重新配置。

四、代码实现:一个动态删除的待办清单

下面是一个完整的例子。你不必关心新增待办的逻辑,只看事件委托部分——只有一个监听器,却能处理所有动态添加的删除按钮。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>事件委托演示|动态删除待办</title>
  <style>
    body { background: #f0f2f5; display: flex; justify-content: center; padding: 2rem; font-family: system-ui; }
    .todo-app { background: white; border-radius: 1rem; padding: 1.5rem; width: 400px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    .add-area { display: flex; gap: 8px; margin-bottom: 1rem; }
    input { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 8px; }
    button { padding: 8px 16px; cursor: pointer; background: #1677ff; color: white; border: none; border-radius: 8px; }
    .delete-btn { background: #ff4d4f; margin-left: 12px; }
    ul { list-style: none; padding: 0; }
    li { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee; }
  </style>
</head>
<body>
<div class="todo-app">
  <h3>✅ 待办清单(事件委托演示)</h3>
  <div class="add-area">
    <input type="text" id="todoInput" placeholder="写一个待办...">
    <button id="addBtn">添加</button>
  </div>
  <ul id="todoList">
    <li>吃饭 <button class="delete-btn">删除</button></li>
    <li>睡觉 <button class="delete-btn">删除</button></li>
  </ul>
  <p style="font-size:12px; color:#888;">⭐ 试试添加新待办,删除按钮照样工作!</p>
</div>

<script>
  // 获取父容器
  const todoList = document.getElementById('todoList');
  const addBtn = document.getElementById('addBtn');
  const input = document.getElementById('todoInput');

  // 添加待办(动态生成新元素)
  function addTodo() {
    const text = input.value.trim();
    if (text === '') return;
    const li = document.createElement('li');
    li.innerHTML = `${text} <button class="delete-btn">删除</button>`;
    todoList.appendChild(li);
    input.value = '';
  }

  // 🔥 事件委托:监听 todoList 上的点击事件
  todoList.addEventListener('click', (event) => {
    // event.target 是实际点击的那个元素(比如按钮、文字等)
    const target = event.target;
    // 判断点击的是不是删除按钮(class 包含 delete-btn)
    if (target.classList && target.classList.contains('delete-btn')) {
      // 删除整个 li(父元素是 li)
      const li = target.closest('li');  // 找到最近的外层 li
      if (li) li.remove();
      console.log('删除了一个待办');
    }
  });

  addBtn.addEventListener('click', addTodo);
  input.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') addTodo();
  });
</script>
</body>
</html>

解释核心三行

  • todoList.addEventListener('click', ...) → 监听父容器,而不是每个按钮。
  • event.target → 你真正点到的那个元素(可能是按钮,也可能是文字)。
  • if (target.classList.contains('delete-btn')) → 只有点删除按钮时才删除对应的 <li>

就这样,不管以后添加多少个新待办,删除功能永远正常,因为事件监听一直挂在 ul 上。


五、事件委托的三大好处

  1. 动态元素自动支持
    后添加的子元素不需要重新绑定事件,省去大量 addEventListener 代码。

  2. 内存占用更低
    原本需要给 100 个按钮绑定 100 个监听器,现在只需要 1 个父容器监听器。

  3. 代码更干净
    不用写 for 循环去逐个绑定,也不用担心元素删除后内存泄漏。


六、什么时候用事件委托?什么时候不用?

用事件委托

  • 有很多同类元素需要相同事件(比如列表里的删除、选中复选框)
  • 元素是动态添加/删除的
  • 希望简化代码、提升性能

不用事件委托

  • 元素很少且是静态的(两三个按钮,直接绑就行)
  • 你需要阻止事件冒泡(stopPropagation)的时候,委托反而会干扰
  • 事件需要精确控制冒泡阶段(例如表单提交前验证)

七、补充:closest 方法的妙用

上面代码用了 target.closest('li'),这是个超级好用的 API:

element.closest(选择器) 会沿着元素本身及其祖先,找到第一个匹配选择器的元素。

比如你点到了删除按钮内部的 <span> 或文字,target 不是按钮本身,但 closest('.delete-btn') 能帮你找到上层的删除按钮,再 closest('li') 找到整个待办项。这样可以避免“点偏了导致删除失败”的 bug。


八、最后一句

事件委托不是一个高深的概念,它就是利用了事件冒泡的“偷懒大法”。记住这个口诀:

子元素动态跑,父容器监听到;
event.target 你是谁,再处理就对了。

赶紧把上面的代码复制到本地跑一跑,亲手添加几个新待办,然后点删除——你会爱上这种一劳永逸的感觉。

评论区留言:你曾经在哪个项目里用事件委托解决了大问题?分享出来,一起避坑~