事件委托需从event.target出发向上遍历parentElement,设容器为边界并用matches()匹配选择器,先校验target是否为Element,匹配成功即break,监听须在冒泡阶段。

<p>事件委托利用父元素监听子元素事件,关键在于从 <code>event.target</code> 出发,用 <code>parentElement</code> 循环向上遍历,直到匹配目标区域(比如某个 class 或 data 属性),而不是直接绑定在深层子节点上。</p>
<H3>明确查找终点和条件</H3>
<p>不能无限制往上找,需设定停止条件:要么到达指定容器(如 <code>container</code>),要么匹配目标选择器(如 <code>[data-action="delete"]</code>)。否则可能越界到 body 或 html,导致误判。</p>
<ul>
<li>推荐写法:<code>while (el && el !== container)</code>,把容器作为安全边界</li>
<li>匹配时优先用 <code>el.matches(selector)</code>,语义清晰且支持复杂选择器(如 <code>.btn[data-type="save"]</code>)</li>
<li>避免只靠 <code>className</code> 或 <code>tagName</code> 判断,易受干扰(比如按钮里有 span,点击 span 时 tagName 是 "SPAN")</li>
</ul>
<H3>正确处理 event.target 的初始值</H3>
<p><code>event.target</code> 可能是文本节点、伪元素代理的元素,或被 shadow DOM 隔离的内容。实际使用前建议先做基础校验:</p>
<ul>
<li>用 <code>event.target instanceof Element</code> 过滤非元素节点(如 TextNode)</li>
<li>若项目用 Web Components,需考虑 <code>event.composedPath()[0]</code> 替代 <code>event.target</code>(兼容 shadow 根内点击)</li>
<li>不建议直接 <code>event.target.parentElement</code> 开始循环——万一 target 就是目标元素,跳过就漏了</li>
</ul>
<H3>循环中及时退出,避免冗余遍历</H3>
<p>匹配成功后应立刻 <code>break</code> 或 <code>return</code>,不要继续往上走。常见错误是写成 <code>for</code> 循环却没设终止条件,或用 <code>forEach</code> 无法中途退出。</p>
<ul>
<li>推荐 while + break 模式:</li>
<li><code>let el = event.target;</code></li>
<li><code>while (el && el !== container) {</code></li>
<li><code> if (el.matches('.item-btn')) { handle(el); break; }</code></li>
<li><code> el = el.parentElement;</code></li>
<li><code>}</code></li>
</ul>
<H3>注意事件捕获与冒泡阶段的影响</H3>
<p>委托依赖事件冒泡,所以监听必须放在冒泡阶段(第三个参数为 <code>false</code> 或省略)。如果用了 <code>useCapture: true</code>,<code>event.target</code> 仍是原始目标,但 <code>parentElement</code> 查找逻辑不变——只是触发时机提前了,容易在中间层拦截掉,一般不用于委托。</p>
<ul>
<li>确保父容器本身没有 <code>pointer-events: none</code> 或 <code>visibility: hidden</code>,否则事件可能无法到达</li>
<li>动态添加的子元素无需重新绑定,只要它们结构符合向上查找路径即可</li>
<li>慎用 <code>stopPropagation()</code> 在中间节点——它会切断委托链,使上级监听器收不到事件</li>
</ul>