JS事件委托、事件代理

JS事件委托、事件代理

JS事件委托、事件代理是面试中经常被问到的一类面试题,形式可能多种多样,但是核心内容都相同。比如我最近面试中遇到的:
朋友圈示例

问题描述

提问:咱们都有微信朋友圈,每一条朋友圈中都会有一个可以点赞、评论的按钮,请问如何对所有按钮添加click事件呢?(如上图)

答:第一反应,给DOM元素添加点击事件有什么难的,给元素一个onclick事件不就行了么?

追问:一个节点好说,但每个人都有许多条朋友圈,都添加点击事件呢?朋友圈这种瀑布流的加载方式,如何对新增的节点添加click事件呢?

答:for循环遍历去添加click事件吧?当往下拉的时候封装一个函数来对添加的数据遍历绑定事件。。。(我意识到此问题没那么简单了)

追问:那如果有500条呢?有没有想过用事件代理来处理?(有点失望)

答:事件代理??。。。

以上就是写这篇文章的起因,那么到底什么是事件代理呢?

1.概述与优点:


事件代理其实跟事件委托是相同的概念,叫法名字不同而已。
在JS中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,访问DOM的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,所以前端性能优化的主要思想之一就是减少DOM操作。利用事件代理(委托),就能大大的减少与DOM的交互次数。
事件代理(委托)是利用事件冒泡机制,只指定一个事件处理程序,代为管理某一类型的所有事件,减少DOM操作及事件绑定,防止内存泄漏,提高性能。

2.经典例子:


有一个经典的收快递的例子,来说一下我自己的解读。
假设某公司有5名员工预计会在周一收到快递(衣服、数码产品…),为了收到这些快递,有两种方案:
一、5名员工在门口等着快递上门;
二、5名员工委托公司前台处理并代收快递
我们先来分析一下方案一:
5名员工站在公司门口等快递,就肯定会影响其他的正常工作。另外,如果收快递的人数是50,500的话,势必会造成公司大门的阻塞。
方案二:
交给公司前台来处理,公司前台处理所有的快递工作,然后判断收件人并按收件人的要求进行签收,甚至代为付款。我们假设公司前台处理能力足够,就算有500,5000件快递都不会造成公司大门阻塞。
另外此种方案还有更大的优势,即使公司来了新的员工(不管多少),公司前台也会在核实后代为签收并转交给新员工。

讲完这个例子,我们来理清例子中出现的人物(公司、快递、员工、前台…)到底代表了什么:
公司 -> 最外层元素(朋友圈例子中的document,整个朋友圈)
快递 -> 事件(朋友圈例子中我们预设的点击事件,对应了预计周一收快递)
前台 -> 父元素(朋友圈例子中的包含所有朋友信息圈的元素)
员工 -> 子元素(朋友圈例子中的按钮)
新员工 -> 新加载的元素(例子中瀑布流加载进来的新的朋友圈)
公司大门 -> 内存(公司大门阻塞会造成卡顿)
相信大家带着这些具象化的内容重新想一遍问题,应该不难理解事件代理(委托)是怎样一种逻辑了。

3.实现原理:


事件代理(委托)是利用事件冒泡原理来实现的,何为事件冒泡呢?
事件会从最深的节点开始,逐步向上传播事件,事件向上传播类似水中的气泡往上升,故叫事件冒泡。
举个例子:页面上有这么一个节点树,div>ul>li>a。若给最内层的a添加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺a>li>ul>div。
通过这个机制,那么我们监听最外面的div元素,那么其中的ul,li,a发生点击事件的时候,都会冒泡到最外层的div元素上,然后委托外层父级代为执行事件操作,这就是事件委托。

4.代码实现:


例子1:
例如这样的节点,想实现的功能是点击li,弹出li中内容

1
2
3
4
5
6
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>

不用事件代理相信大家都会这样实现,

1
2
3
4
5
6
7
8
9
window.onload = function(){    
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(this.innerHTML);
}
}
}

我们看看有多少次的dom操作,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作。

如果利用事件代理会是怎样的呢?代码如下:

1
2
3
4
5
6
7
8
9
10
window.onload = function(){
  var oUl = document.getElementById("ul1");
  oUl.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'li'){
        alert(target.innerHTML);
    }
  }
}

注:Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源。也就是说,target就可以表示为当前的事件操作的dom,标准浏览器用ev.target,IE浏览器用event.srcElement。我们用target.nodeName来获取具体的标签名,返回值是大写的(转成小写进行判断,符合通用习惯)
这样一来,我们只需执行一次DOM操作!如果DOM数量巨大的话,性能的优化将非常明显。

例子2:
上面的例子中所有li的操作是相同的效果,如果每个li被点击的效果都不一样,那么用事件委托该如何用呢?
例如这样的节点,想实现的功能是点击input,弹出input的value值。

1
2
3
4
5
6
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>

不用事件代理的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
window.onload = function(){
var Add = document.getElementById("add");
var Remove = document.getElementById("remove");
var Move = document.getElementById("move");
var Select = document.getElementById("select");
Add.onclick = function(){
alert('添加');
};
Remove.onclick = function(){
alert('删除');
};
Move.onclick = function(){
alert('移动');
};
Select.onclick = function(){
alert('选择');
}
}

4种类型的按钮,点击每一个做不同的操作,那么至少需要4次dom操作

利用事件代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}

只用一次dom操作就能完成所有的效果。(回想一下举例中收到不同快递的情况)
特别说明:着重说明的是,新添加的子元素是带有事件效果的,我们不用费力的去给新加入的元素添加事件绑定
以上这些例子能概括的说明事件代理(委托)的实现原理,但不能代表所有事件代理(委托)的发生情况

5.适用范围:

适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress

注:
1.mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要经常计算它们的位置,处理起来不太容易,不建议用此方法
2.mousemove事件,使用时要一直计算它的位置,不建议用此方法

不适合事件委托的事件:focus,blur之类的事件,本身不具有冒泡特性,无法适用事件代理(委托)

focus,blur之类的事件,本身不具有冒泡特性,无法适用事件代理(委托)

6.结语:

事件代理(委托)能让我们不需要去遍历子元素节点,只需要在父元素添加事件代理(委托)就能对子元素实现事件绑定,极大程度上减少了DOM操作,这才是事件委托的精髓!

看完这篇文章,相信大家对处理大量节点绑定事件的问题会有更好的思路,能更好的回答面试中经常被问到各种情况的事件代理问题,减少不必要的尴尬  ̄□ ̄||。

以上文章均为个人拙见,如有错误请不吝指正!
2018年3月24日凌晨 in Beijing!

文章作者: fxgao
文章链接: http://www.fxflying.com/2019/08/20/eventProxy/
版权声明: 转载请注明来自 fxgao!'
打赏
  • 微信
  • 微信pay
  • 支付宝pay

评论