事件委托浅谈

431 阅读5分钟

事件委托,还有一个名字叫事件代理,都是一种包装后的非常高大上的说法。

这是一个面试中常常会遇到的高频问题,谈谈你对事件委托的理解。其实它理解起来很简单,是前端工作中常用且基础的一个东西。

JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。 那这是什么意思呢?网上的各路大牛们讲事件委托基本上都用了同一类例子,就是取快递。通过日常生活中最常见的小事来解释这个现象,能让我们更直观地领会事件委托的原理:

举例——收快递

有三个同事 a, b, c 预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台代为签收,之后再去领取。现实中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口等快递)。前台收到快递后,会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台也会在收到寄给新员工的快递后核实并代为签收。

这里其实还隐含有2层意思:

  1. 在编员工委托前台的同事是可以代为签收的,即程序中的现有的DOM节点是有事件的;
  2. 新员工也是可以被前台代为签收的,即程序中新添加的DOM节点也是有事件的。

应用背景

在JS中,添加到页面上的事件处理响应都会占用内存,随着内存占用的越多用户电脑剩余资源就越少,运行速度降低体验变差;且如果事先就指定好所有的事件处理,导致的DOM操作频繁,重绘页面次数增多,会延迟整个页面的交互响应就绪时间。特别是形如对ul的li添加事件处理响应,如果是给大量子元素添加事件,会占用大量内存。想想一个公司所有人排队在门口取快递的情景,是不是很有体会。事件处理绑定的越多越影响性能,但是我们又必须满足需求实现功能,所以我们需要一种方法来减少绑定的事件,减少DOM操作,使用事件委托。即让前台帮忙签字代收快递。

原理

事件委托是利用事件的冒泡原理来实现的。就是事件从最内层的节点开始,然后逐步向上传播事件,一层一层的往外执行触发响应,执行顺序由内而外。如果有这样一个机制,那么我们给最外面的节点如div添加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。

在触发DOM上的某个事件的时候,就会产生一个事件对象event,这个对象中包含着所有与事件有关的信息,其中包括导致事件的元素、事件的类型以及事件相关的信息。避免对特定的每个节点添加事件监听占用大量内存 ,而是使用事件监听器添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件,并代为触发响应。

实现案例

例如:

<ul>
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
</ul>

如果想实现ul里li元素批量添加点击响应,但有时这些li元素可能会被删除,可能会有新增,监听它们的事件会非常麻烦。这时就会用到我们提到的事件委托了。但如果只是简单把监听器放在父元素上,点击该父元素的无论哪一块都会触发响应,我们只想让对应子元素响应对应事件,怎么办。监听器放在父元素上,如何能知道是那个子元素触发事件?其实很简单:当子元素的事件冒泡到父ul元素时,你可以检查事件对象的target属性,获取真正事件源,节点元素的引用

window.onload = function(){
    let ul1 = document.querySelector('ul');
    ul1.onclick = function(e){
       let e = e || window.event;
    let target = e.target || e.srcElement;
    if(target.nodeName.toLowerCase() == 'li'){
           target.style.background = "red";
    }
}

这里用到事件源,它是event 对象,不管在哪个事件中,只要你操作的那个元素就是事件源。
ie:window.event.srcElement
标准下:event.target

使用事件委托的方式,新添加的子元素是天然带有事件响应的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好。这样可以大大的减少DOM操作频率,这就是事件委托的最大优点所在。

###小结 事件委托是那些被绑定到父级元素的事件,满足一定的匹配条件时,使得子元素触发相应的响应。这是靠事件的冒泡机制来实现的。

优点

  • 可以大量节省内存占用,减少响应事件注册,比如在 ul 上代理所有 li 的click事件
  • 可以实现当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适

缺点

如果把所有事件都用委托就可能会出现误判,即本不应用触发事件的子元素被绑上了事件。当要触发事件的标签里面还有其他标签时,不能正常的委托,因为target或srcElemt代表里面任意的标签元素。