Skip to content
索引

事件委托

基本概念

事件委托,就是将本该添加在当前点击的元素的事件,添加到别的元素上来完成这一事件,利用的是事件冒泡机制

一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数

举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学

在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个

事件冒泡

前面提到 DOM 中事件委托的实现是利用事件冒泡的机制,那么事件冒泡是什么呢?

在 document.addEventListener 的时候我们可以设置事件模型:事件冒泡、事件捕获,一般来说都是用事件冒泡的模型

事件模型是指分为三个阶段:

  • 捕获阶段:在事件冒泡的模型中,捕获阶段不会响应任何事件
  • 目标阶段:目标阶段就是指事件响应到触发事件的最底层元素上
  • 冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层(根节点),事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层

委托的优点-减少内存消耗

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

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

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

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

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

委托的优点-动态绑定事件

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

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

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

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

Vue巧用事件委托

开发中经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。

绑定这么多监听,从性能方面来说是不太好的。就可以使用事件委托的方式来优化

js
//html
<table @click="edit">
  <tr v-for="item in list">
    <td>{{item.name}}</td>
    ...
    <td>
      <button :data-id="item.id" title="eidt">编辑</button>
   </td>
  </tr>
</table>


//js 
edit (event){
    // target是鼠标点击的直接元素,所以委托者最好不要有很多子元素
    if(event.target.title == "edit"){ //如果点击到了edit 
        let id = evenr.target.dataset.id;
        //拿着id参数执行着相关的操作
    }
}
//html
<table @click="edit">
  <tr v-for="item in list">
    <td>{{item.name}}</td>
    ...
    <td>
      <button :data-id="item.id" title="eidt">编辑</button>
   </td>
  </tr>
</table>


//js 
edit (event){
    // target是鼠标点击的直接元素,所以委托者最好不要有很多子元素
    if(event.target.title == "edit"){ //如果点击到了edit 
        let id = evenr.target.dataset.id;
        //拿着id参数执行着相关的操作
    }
}

事件冒泡/事件捕获

现有父子孙三层 dom 元素,依次包裹,大小相同,且三层 dom 都有点击事件

html
<div class="father">
  <div class="son">
    <div class="grandson"></div>
  </div>
</div>
<div class="father">
  <div class="son">
    <div class="grandson"></div>
  </div>
</div>

事件冒泡执行过程:点击 dom 元素,事件依次向上(向父一级)开始触发,触发顺序为孙>子>父

事件捕获执行过程:点击 dom 元素,事件依次向下(向子一级)开始触发,触发顺序为父>子>孙

event.stopPropagation

这是阻止事件冒泡的方法,不让事件向上冒泡,但是默认事件仍然会执行

event.preventDefault

这是阻止默认事件的方法,调用此方法,默认事件会被阻止,但是会发生冒泡,冒泡会传递到上一层的父元素

return false

阻止事件的冒泡方法和默认事件

Released under the MIT License.