先记住这个答案
在父节点绑定监听器,利用事件冒泡,子节点触发的同一类型事件总会传到父节点。通过读取 event.target 获取真实触发元素,再用 closest() 等方法判断是否为所需目标,从而简化大量子元素的绑定管理。动态添加的节点只需复用既有父监听,无需额外注册;新增元素触发的事件同样冒泡且被父监听捕获。
- 委托依赖冒泡,父级统一监听即可
- 用 target closest 判断具体触发元素
- focus 等不冒泡事件需捕获方式
事件上浮路径与统一接收
DOM 中点击任意元素,click 事件会先沿父链捕获到目标节点,再按原路线冒泡回 window。因此,目标节点的祖先均有机会在冒泡阶段收到该事件。如果我们在一个稳定的父节点挂载监听器,就能统一接收其所有后代触发的可冒泡事件,而无需为每个后代单独绑定监听函数。
事件委托不依赖子节点是否存在吗?并不依赖。只要触发事件的实际节点与目标节点位于同一条事件路径中,事件就能到达父节点。比如一行后续由异步数据渲染的列表项,用户点击它时事件照样会沿 path 到达容器,于是动态新增的内容可以不加任何显式绑定即获得处理。这个机制也因此让大量同类子节点共享一个监听器,而不是每新增一个元素就新建监听函数。
任务列表动态删除的实际实现
假设一个任务页面:<ul id="taskList"> 初始为空,用户提交后通过接口插入 <li class="task-item"><button class="delete">删除</button></li>。如果对每个 delete 按钮直接绑定 click,必须在插入时手工挂载,并且后续移除时还要考虑解绑,代码容易出现遗漏与耦合。考虑用委托,只在 taskList 上绑定一次。
事件处理函数内,先读取 const deleteBtn = event.target.closest('.delete'),如果为空就忽略此次点击。拿到按钮后,再用 button.closest('.task-item') 得到真实行项目并移除。新增的列表项不需要任何额外代码,因为按钮点击引发的 click 会自行冒泡到 ul 并由统一 handler 判定。这样避免了手动绑定管理,也让新任务删除功能与原逻辑完全解耦。
只适用于可冒泡且可命中的事件
委托依赖事件必须经过父节点。对于 focus、blur、mouseenter、mouseleave 这类不参与冒泡的行为,简单把它绑定到父节点不会工作;focus 和 blur 可通过捕获阶段在祖先上捕获,或改用 focusin/focusout 等冒泡事件;mouseenter 和 mouseleave 则需要使用 mouseover/mouseout 或捕获方式来获得类似效果。另外,若内部某个监听器调用了 stopPropagation(),事件将被截停不再向上,父级委托也会失效。
命中判断也需细致。比如用户可能点击的是按钮内部的一个 span,event.target 才是那个文字节点,但 closest('.delete') 能自动向上找,正确处理能保证委托对更多层内嵌节点生效。若页面大量使用 Shadow DOM 或跨文档 iframe,事件只能限定在各自文档边界内传播,必须单独为每个边界配置委托,否则无法统一管理外部监听。在这些场景中,明确事件路径与限制才能决定是否能采用现有思路。
容易答错的地方
- 认定所有事件都能靠冒泡委托
- 不正确。例如
focus、blur、mouseenter与mouseleave都不会在冒泡阶段向上传播,单纯配置在父节点无效。正确的扩展方式是:对focus/blur可使用focusin/focusout或显式开启捕获;对mouseenter/mouseleave应改用mouseover/mouseout或捕获模式,从而获得类似效果。 - 动态新增后仍需重新执行绑定
- 误解。委托的监听器早已存在于祖先节点,新增元素触发的事件会自然冒泡到同一祖先,祖先上的处理器能够直接访问
event.target判别事件源。除去要在原生脚本中手动加上 DOM 操作外,并不需要为每个新节点重复调用绑定函数。
面试官还会怎么问?
点击文字节点时 `event.target` 是什么?
是包含该文字的最深元素节点,而不是 Text 节点。浏览器在派发事件时会将 target 归一化成元素。所以即便点中文字,也能用 closest() 继续向上寻找匹配的父容器。
如何用冒泡委托处理不冒泡的 `focus` 和 `blur`?
它们不冒泡,但可以在捕获阶段拦截。把监听器以 { capture: true } 绑定在祖先上,同样能捕获到后代元素的焦点事件,达到类似委托的效果;或者换成带冒泡的 focusin/focusout 事件。
若中间元素调用 `stopPropagation()` 会怎样?
事件被截停后不再继续沿传播路径(如冒泡)向上传播,外层父级监听器就收不到。所以委托的全局有效前提是,事件路径上不得存在停止冒泡的逻辑。一旦使用该方法,依赖其外层委托的代码会中断。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。