前端阅读 95月28日 03:39
什么是事件代理?原理、优缺点和应用场景是什么?
事件代理(事件委托)是利用事件冒泡机制,将子元素的事件监听器统一绑定到父元素上的一种模式。面试中常从原理、优缺点、边界问题、实战场景四个层面考察。核心原理DOM 事件流经历三个阶段:捕获阶段(从 window 向下传播到目标元素)→ 目标阶段(事件到达目标元素)→ 冒泡阶段(从目标元素向上传播回 window)。事件代理利用的就是冒泡阶段——子元素触发事件后,事件沿 DOM 树逐层向上传播,因此在父元素上可以统一捕获并处理。// 传统方式:每个子元素各自绑定,N 个元素需要 N 个监听器document.querySelectorAll('li').forEach(li => { li.addEventListener('click', handler);});// 事件代理:只在父元素绑定一次,无论多少子元素都只需 1 个监听器document.querySelector('ul').addEventListener('click', (e) => { if (e.target.matches('li')) { handler(e); }});优点减少内存占用:100 个按钮只需 1 个监听器,而非 100 个,显著降低内存消耗动态元素自动响应:新增的子元素无需重新绑定,天然具备事件响应能力,特别适合动态渲染的列表减少 DOM 操作:绑定和解绑只涉及父元素,降低与 DOM 的交互次数代码更易维护:事件处理逻辑集中在父元素,修改时只需改一处缺点不适用于不冒泡的事件:focus、blur、scroll、mouseenter/mouseleave 不冒泡,无法使用事件代理(可改用 focusin/focusout,它们冒泡)嵌套元素干扰判断:子元素内部还有子元素时,e.target 可能不是期望的目标元素非目标点击误触发:父元素区域内非目标元素的点击也会进入回调,需要手动过滤层级过深可能被拦截:冒泡链路中间如果调用了 stopPropagation(),事件无法到达代理层嵌套子元素干扰如何解决用 e.target.closest('li') 替代 e.target.matches('li')。closest 会沿 DOM 树向上查找最近匹配的祖先元素,即使点击的是 li 内部的 span 也能正确定位。而 matches 只检查元素自身,不向上查找。// matches 版本:点击 li 内的 span 会匹配失败ul.addEventListener('click', (e) => { if (e.target.matches('li')) handler(e); // 内部有 span 时失效});// closest 版本:点击 li 内的 span 仍能找到 liul.addEventListener('click', (e) => { const li = e.target.closest('li'); if (li) handler(e);});e.target 与 e.currentTarget 的区别e.target:实际触发事件的最深层元素(用户真正点击的那个元素)e.currentTarget:绑定监听器的元素,在事件代理中就是父元素代理场景下两者始终不同:e.currentTarget 是挂载监听器的父元素,e.target 是用户实际点击的子元素。理解这个区别是掌握事件代理的关键。实际应用场景列表/表格的行点击:导航菜单选中、数据表格行操作动态表单项:可增减的输入行、标签列表的添加与删除React 合成事件体系:React 17 前将所有事件代理到 document,17+ 代理到 root 节点,本质上就是事件代理思想在框架层的工程化实践事件代理 + 防抖:在滚动容器上代理子元素的点击,配合防抖避免误触就近委托是最佳实践:在最近的公共父元素上代理,而非一律挂载到 document 或 body,这样可以减少不必要的事件冒泡路径和回调触发次数。