事件传播分捕获、目标、冒泡三阶段;委托应监听父容器并用e.target识别真实触发元素;stopPropagation()阻断传播但不阻止同元素其他监听器,stopImmediatePropagation()则完全中断。

事件传播的三个阶段:捕获、目标、冒泡
JavaScript事件不是“点哪响哪”,而是按固定路径在DOM树中走一趟:从document开始向下捕获(捕获阶段),到达被点击的真实元素(目标阶段),再原路返回到document(冒泡阶段)。默认所有addEventListener监听器都在冒泡阶段触发,也就是你平时写的el.addEventListener('click', handler)等价于el.addEventListener('click', handler, false)。
若想在捕获阶段介入(比如全局拦截权限校验),得显式传true:el.addEventListener('click', handler, true)。注意:捕获和冒泡是两条独立通道,同一事件可同时在父元素捕获、子元素目标、父元素冒泡——别指望一个stopPropagation()能跨阶段拦住全部。
事件委托怎么写:用e.target而不是this
委托的核心就一句话:把监听器挂到稳定父容器上,靠e.target识别真正被点的是谁。常见错误是误用this——它永远指向绑定监听器的那个父元素,不是你想要的子项。
- ✅ 正确写法:
list.addEventListener('click', e => { if (e.target.matches('.delete-btn')) { /* 处理删除 */ } }) - ❌ 错误写法:
if (this.classList.contains('delete-btn'))——this是list,永远不匹配 - 优先用
e.target.matches(selector)而非e.target.tagName,更灵活且兼容类名/属性等复杂条件 - 确保父容器存在且不会被频繁替换,比如选
比选更靠谱,后者判断成本高还易误伤
为什么委托能省内存、支持动态元素
传统方式为100个各绑一个click监听器,就是100个函数实例+100次DOM查询;委托只绑1次,无论后续appendChild多少新,都不用改JS——因为事件会自动从新元素冒泡上来。
立即学习“Java免费学习笔记(深入)”;
- Chrome性能数据显示:千级列表项下,委托比逐个绑定内存占用低约80%
- 动态添加后无需
removeEventListener再addEventListener,彻底规避解绑遗漏导致的内存泄漏 - 但注意:如果子元素设置了
pointer-events: none或disabled(如),事件根本不会触发,委托也就失效了
stopPropagation()和stopImmediatePropagation()的区别
两者都叫“阻止传播”,但作用范围不同: 实际开发中,最常被忽略的是事件是否真的冒泡——e.stopPropagation()只停当前事件流(比如阻止冒泡到父e.stopImmediatePropagation()会立刻中断,连同层其他addEventListener回调都不跑了。
stopPropagation():万一父容器自己也要响应点击(比如折叠菜单),子项一拦,父逻辑就断了stopImmediatePropagation()调试时容易踩坑——加了个新监听器却没生效,可能只是被前面某个handler悄悄掐掉了e.preventDefault(),它和传播控制互不干扰focus、blur默认不冒泡,得换用focusin/focusout;change、input虽冒泡,但某些老浏览器对input事件时机处理不一致,上线前务必实测。











