事件委托

161 阅读2分钟

基本概念

简而言之,事件委托就是将一个元素响应事件(click、keydown......)的函数委托到其上级的另一个元素; 一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

使用事件委托的优点

1. 减少内存消耗

假设存在一个列表,需要对每一个列表项的点击事件做出响应,代码如下所示

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

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素,相对会节省内存

2. 动态绑定事件

多数情况中,代码需要根据用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。而利用事件委托机制后,事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配。

实例

假设一个div1中存在多个button,让每个button被点击都能出发对应事件

//要实现的封装效果如下:
on('click','#div1','button',()=>{
    console.log('button is been clicked')
})

//封装内容如下:
function on(eventType,element,selector,fn){
    if(!(element instanceof Element)){
        element = document.querySelector(element)
    }
    element.addEventListener(eventType,(e)=>{
        const t=e.target;
        if(t.matches(selector)){
            fn(e);
        }
    })
}

但是该方法不完全正确,我们还需要添加一个递归判断,来防止被其他元素包裹无法匹配:

on: function(element, eventType, selector, fn) {
    element.addEventListener(eventType, e => {
      let el = e.target
      while (!el.matches(selector)) {
        if (element === el) {
          el = null
          break
        }
        el = el.parentNode
      }
      el && fn.call(el, e, el)
    })
    return element
  }

局限性

  1. focus、blur 之类的事件本身没有事件冒泡机制,无法委托。
  2. mousemove、mouseout之类的需要频繁计算的事件,需要不断通过位置去计算定位,对性能消耗高,不适合使用事件委托。