浅析JavaScript中的事件委托

117 阅读3分钟

JavaScript 中一个重要的方法就是事件委托(又叫事件代理)。
事件委托将事件侦听器添加到一个父级元素上,这样就只用添加一次事件侦听器,可以避免向 (父级元素内)很多特定的 DOM 节点添加多个事件侦听器,减少了内存消耗,从而优化程序性能。
而这个添加在父元素上的事件侦听器通过事件冒泡的事件流机制以分析查找子元素的匹配项。

委托的优点

1. 减少内存消耗

试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li


如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;

因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

所以事件委托可以减少大量的内存消耗,节约效率。

2. 动态绑定事件

比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件;

在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的

事件委托的实现

假设有一个ul带有多个子元素的父元素

<ul id="list" class="list">
  <li id="item-1" class="item">Item 1</li>
  <li id="item-2" class="item">Item 2</li>
  <li id="item-3" class="item">Item 3</li>
  <li id="item-4" class="item">Item 4</li>
  <li id="item-5" class="item">Item 5</li>
  <li id="item-6" class="item">Item 6</li>
</ul>

由于事件委托是将事件侦听器添加到父级,如何知道单击了哪个子元素成为了要解决的最大问题。

处理方式其实很简单,当点击 UL 元素下的任何子元素,当事件冒泡到 UL 元素时,通过检查事件对象的 target 属性就可以获得对实际单击子节点的引用。简单的 JavaScript 实现如下:

const $list = document.querySelector('#list')
// 获取元素,添加点击监听器... 
$list.addEventListener('click', function (e) {
    // e.target 是被点击的元素!
    const $li = e.target
    // 如果它是一个列表项
    if ($li && $li.tagName.toLowerCase() === 'li') {
        // List项目找到了!输出的ID!
        console.log(`list ${$li.id} 被点击了`);
    }
})

可以看到,事件委托之所以能够正常工作,最重要的原因就是事件冒泡事件流机制。点击 UL 下的子元素,由于事件冒泡,在 UL 元素上的 click 事件侦听器也会被触发。而这时,可以通过 event.target 获取到点击的目标元素。再通过对这个元素的一系列的判断检测是否为我们期望的元素,如果是就执行相关的操作。

使用事件委托的好处不仅在于将多个事件处理函数减为一个,而且对于不同的元素可以有不同的处理方法。假如上述列表元素当中添加了其他的元素节点(如:a、span等),就不必再一次循环给每一个元素绑定事件,直接修改事件委托的事件处理函数即可。