事件代理(事件委托)

1. 什么是事件代理

把一个元素响应事件(click、keydown 等)的函数委托到另一个元素。

事件流都经过三个阶段:捕获 → 目标 → 冒泡,事件委托就是利用冒泡阶段完成的:把一个或一组元素的事件委托到它的父层或更外层元素,真正绑定事件的是外层元素;当事件响应到目标元素时,通过事件冒泡触发外层元素的绑定函数,再在外层元素上去执行。

适合事件委托的事件:click、mousedown、mouseup、keydown、keyup、keypress。

2. 应用场景

一、减少内存消耗

如果给每个列表项都绑定一个函数,内存开销很大。改用事件委托,把点击事件绑在父层 <ul>,执行时再去匹配目标元素:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item n</li>
</ul>

document.getElementById('list').addEventListener('click', function (e) {
  var target = e.target || e.srcElement;
  if (target.nodeName.toLowerCase() === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

二、动态绑定事件

如果用户可以随时动态增加或移除列表项,每次都要重新给新增的元素绑定、给删除的元素解绑,非常麻烦。事件委托绑在父层,和目标元素的增减无关。

const oBtn = document.getElementById('btn');
const oUl = document.getElementById('ul1');

oUl.onclick = function (ev) {
  const target = ev.target || ev.srcElement;
  if (target.nodeName.toLowerCase() === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
};

oBtn.onclick = function () {
  const oLi = document.createElement('li');
  oLi.innerHTML = 'item ' + ++num;
  oUl.appendChild(oLi);
};

3. 优缺点

优点:

  • 减少内存消耗(无需为每个子元素绑定)。
  • 动态增删子元素时无需重新绑定。
  • 简化逻辑,统一在外层处理。

缺点:

  • 把所有事件都用代理可能会出现事件误判(本不该触发的事件被绑上)。
  • 依赖事件冒泡,focus / blur 等不冒泡的事件需要特殊处理(用 focusin / focusout 或 mouseenter / mouseleave 模拟)。
  • 层级太深或子元素过多时,事件冒泡阶段需要逐级判断目标元素,有轻微性能损耗(实际可忽略)。

来源整理自:vue3js.cn 面试官系列