事件冒泡是dom默认行为,事件从目标元素逐层向上触发至根节点;事件委托利用冒泡机制,将监听器绑定在父元素上,通过event.target.closest()安全获取真正点击的子元素。

事件冒泡不是bug,是DOM默认行为
点击一个 <button></button>,它的父 <div>、祖父 <code><section></section> 甚至 document 都会收到这个 click 事件——这不是意外,是浏览器按规范走的流程。事件从目标元素出发,逐层向上“冒”到根节点,中间每层只要绑了同类型监听器就会触发。
- 冒泡阶段是默认开启的,无需额外设置;捕获阶段需显式传
true给addEventListener()第三个参数 - 不是所有事件都冒泡:比如
focus、blur、mouseenter、mouseleave默认不冒泡 - 阻止冒泡用
event.stopPropagation(),别用已废弃的event.cancelBubble = true - 注意:调用
stopPropagation()后,同一事件流中更上层的监听器(包括 document 上的)将完全收不到该事件
事件委托就是“让爸爸代签收快递”
你不用给每个 <li> 单独绑 click,而是把监听器挂在它们共同的父级 <ul></ul> 上,等点击发生后,靠 event.target 查出到底点的是哪个 <li>——这就是委托的本质。
- 必须依赖冒泡:如果事件不往上跑,父元素根本收不到子元素的点击
-
event.target是**真正被点击的元素**,可能是个<span></span>或<img alt="什么是JavaScript事件冒泡与事件委托【教程】" >,不是你期望的<li>;常用event.target.closest('li')安全获取目标项 - 动态添加的元素自动生效:后续用
appendChild()或innerHTML加的<li>,无需重新绑定 - 别在委托处理器里直接操作
event.target的样式或属性,除非你确认它就是你要处理的节点类型
为什么不能只写 if (event.target.tagName === 'LI')
看似简单,但实际容易翻车。因为用户可能点的是 <li> 里的文字、图标或按钮,此时 event.target 是那个子元素,tagName 就不是 'LI' 了。
document.getElementById('myList').addEventListener('click', function(event) {
// ❌ 危险:点到 li 里的 span 就失效
if (event.target.tagName === 'LI') {
console.log('OK');
}
// ✅ 推荐:向上找最近的 li
const item = event.target.closest('li');
if (item) {
console.log('Clicked item:', item.textContent);
}
});
-
closest()兼容性好(IE 不支持,但现代项目基本可忽略);若需兼容老 IE,可用循环遍历parentNode - 避免用
event.currentTarget判断目标——它永远是绑定监听器的那个父元素,不是你点的那个 - 委托时别忘了考虑事件类型:比如
input事件不冒泡,得用change或监听input的父容器并用focusin/focusout替代
什么时候不该用事件委托
委托不是银弹。有些场景硬套反而增加复杂度或引入 bug。
立即学习“Java免费学习笔记(深入)”;
- 元素数量极少(比如就 3 个按钮),直接绑定更清晰,没性能压力
- 需要精确控制事件顺序或依赖
event.preventDefault()阻止默认行为,且子元素类型混杂(如表单内同时有<button></button>和<a></a>),委托里判断逻辑易出错 - 父容器本身有滚动、拖拽等交互,事件委托可能干扰其原生行为(例如
touchmove被误拦截) - 使用 Web Components 或 Shadow DOM 时,冒泡默认被截断,委托需显式配置
composed: true








