js中的事件委托(事件代理)详解
本⽂转载:
js中的事件冒泡、事件委托是js 中⼀些需要注意的⼩知识点,这⾥结合转载⽂章总结⼀下:
事件冒泡:JS中当出发某些具有冒泡性质的事件是,⾸先在触发元素寻是否有相应的注册事件,如果没有再继续向上级⽗元素寻是否有相应的注册事件作出相应,这就是事件冒泡。
事件委托:利⽤事件冒泡的特性,将本应该注册在⼦元素上的处理事件注册在⽗元素上,这样点击⼦元素时发现其本⾝没有相应事件就到⽗元素上寻作出相应。这样做的优势有:1.减少DOM操作,提⾼性能。2.随时可以添加⼦元素,添加的⼦元素会⾃动有相应的处理事件。
转载⽂章内容:
起因:
1、这是前端⾯试的经典题型,要去⼯作的⼩伙伴看看还是有帮助的;
2、其实我⼀直都没弄明⽩,写这个⼀是为了备忘,⼆是给其他的知其然不知其所以然的⼩伙伴们以参考;
概述:
那什么叫事件委托呢?它还有⼀个名字叫事件代理,JavaScript⾼级程序设计上讲:事件委托就是利⽤事件冒泡,只指定⼀个事件处理程序,就可以管理某⼀类型的所有事件。那这是什么意思呢?⽹上的各位⼤⽜们讲事件委托基本上都⽤了同⼀个例⼦,就是取快递来解释这个现象,我仔细揣摩了⼀下,这个例⼦还真是恰当,我就不去想别的例⼦来解释了,借花献佛,我摘过来,⼤家认真领会⼀下事件委托到底是⼀个什么原理:
有三个同事预计会在周⼀收到快递。为签收快递,有两种办法:⼀是三个⼈在公司门⼝等快递;⼆是委托给前台MM代为签收。现实当中,我们⼤都采⽤委托的⽅案(公司也不会容忍那么多员⼯站在门⼝就为了等快递)。前台MM收到快递后,她会判断收件⼈是谁,然后按照收件⼈的要求签收,甚⾄代为付款。这种⽅案还有⼀个优势,那就是即使公司⾥来了新员⼯(不管多少),前台MM也会在收到寄给新员⼯的快递后核实并代为签收。
javascript说明这⾥其实还有2层意思的:
第⼀,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第⼆,新员⼯也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。
为什么要⽤事件委托:
⼀般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?⽐如我们有100个li,每个li都有相同的click点击事件,可能我们会⽤for循环的⽅法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在JavaScript中,添加到页⾯上的事件处理程序数量将直接关系到页⾯的整体运⾏性能,因为需要不断的与dom节点进⾏交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页⾯的交互就绪时间,这就是为什么性能优化的主要思想之⼀就是减少DOM操作的原因;如果要⽤事件委托,就会将所有的操作放到js程序⾥⾯,与dom的操作就只需要交互⼀次,这样就能⼤⼤的减少与dom的交互次数,提⾼性能;
每个函数都是⼀个对象,是对象就会占⽤内存,对象越多,内存占⽤率就越⼤,⾃然性能就越差了(内存不够⽤,是硬伤,哈哈),⽐如上⾯的100个li,就要占⽤100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果⽤事件委托,那么我们就可以只对它的⽗级(如果只有⼀个⽗级)这⼀个对象进⾏操作,这样我们就需要⼀个内存空间就够了,是不是省了很多,⾃然性能就会更好。
事件委托的原理:
事件委托是利⽤事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例⼦:页⾯上有这么⼀个节点树,div>ul>li>a;⽐如给最⾥⾯的a加⼀个click点击事件,那么这个事件就会⼀层⼀层的往外执⾏,执⾏顺序a>li>ul>div,有这样⼀个机制,那么我们给最外⾯的div加点击事件,那么⾥⾯的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们⽗级代为执⾏事件。
事件委托怎么实现:
终于到了本⽂的核⼼部分了,哈哈,在介绍事件委托的⽅法之前,我们先来看⼀段⼀般⽅法的例⼦:
⼦节点实现相同的功能:
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
实现功能是点击li,弹出123:
var oUl = ElementById("ul1");
var aLi = ElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}
上⾯的代码的意思很简单,相信很多⼈都是这么实现的,我们看看有多少次的dom操作,⾸先要到ul,然后遍历li,然后点击li的时候,⼜要⼀次⽬标的li的位置,才能执⾏最后的操作,每次点击都要⼀次li;
那么我们⽤事件委托的⽅式做⼜会怎么样呢?
var oUl = ElementById("ul1");
alert(123);
}
}
这⾥⽤⽗级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这⾥当点击ul的时候,也是会触发的,那么问题就来了,如果我想让
事件代理的效果跟直接给节点的事件效果⼀样怎么办,⽐如说只有点击li才会触发,不怕,我们有绝招:
Event对象提供了⼀个属性叫target,可以返回事件的⽬标节点,我们成为事件源,也就是说,target就可以表⽰为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器⽤ev.target,IE浏览器⽤event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这⾥我们⽤nodeName来获取具体是什么标签名,这个返回的是⼀个⼤写的,我们需要转成⼩写再做⽐较(习惯问题):
var oUl = ElementById("ul1");
lick = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
LowerCase() == 'li'){
alert(123);
alert(target.innerHTML);
}
}
}
这样改下就只有点击li会触发事件了,且每次只执⾏⼀次dom操作,如果li数量很多的话,将⼤⼤减少dom的操作,优化的性能可想⽽知!
上⾯的例⼦是说li操作的是同样的效果,要是每个li被点击的效果都不⼀样,那么⽤事件委托还有⽤吗?
<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>
var Add = ElementById("add");
var Remove = ElementById("remove");
var Move = ElementById("move");
var Select = ElementById("select");
alert('添加');
};
alert('删除');
};
alert('移动');
};
alert('选择');
}
}
上⾯实现的效果我就不多说了,很简单,4个按钮,点击每⼀个做不同的操作,那么⾄少需要4次dom操作,如果⽤事件委托,能进⾏优化吗?
var oBox = ElementById("box");
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
LocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}
⽤事件委托就可以只⽤⼀次dom操作就能完成所有的效果,⽐上⾯的性能肯定是要好⼀些的
现在讲的都是document加载完成的现有dom节点下的操作,那么如果是新增的节点,新增的节点会有事件吗?也就是说,⼀个新员⼯来了,他能收到快递吗?
看⼀下正常的添加节点的⽅法:
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
现在是移⼊li,li变红,移出li,li变⽩,这么⼀个效果,然后点击按钮,可以向ul中添加⼀个li⼦节点
var oBtn = ElementById("btn");
var oUl = ElementById("ul1");
var aLi = ElementsByTagName('li');
var num = 4;
//⿏标移⼊变红,移出变⽩
for(var i=0; i<aLi.length;i++){
aLi[i].onmouseover = function(){
this.style.background = 'red';
};
aLi[i].onmouseout = function(){
this.style.background = '#fff';
}
}
//添加新节点
num++;
var oLi = ateElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
这是⼀般的做法,但是你会发现,新增的li是没有事件的,说明添加⼦节点的时候,事件没有⼀起添加进去,这不是我们想要的结果,那怎么做呢?⼀般的解决⽅案会是这样,将for循环⽤⼀个函数包起来,命名为mHover,如下:
var oBtn = ElementById("btn");
var oUl = ElementById("ul1");
var aLi = ElementsByTagName('li');
var num = 4;
function mHover () {
//⿏标移⼊变红,移出变⽩
for(var i=0; i<aLi.length;i++){
aLi[i].onmouseover = function(){
this.style.background = 'red';
};
aLi[i].onmouseout = function(){
this.style.background = '#fff';
}
}
}
mHover ();
//添加新节点
num++;
var oLi = ateElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
mHover ();
};
}
虽然功能实现了,看着还挺好,但实际上⽆疑是⼜增加了⼀个dom操作,在优化性能⽅⾯是不可取的,那么有事件委托的⽅式,能做到优化吗?
var oBtn = ElementById("btn");
var oUl = ElementById("ul1");
var aLi = ElementsByTagName('li');
var num = 4;
//事件委托,添加的⼦元素也有事件
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
LowerCase() == 'li'){
target.style.background = "red";
}
};
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
LowerCase() == 'li'){
target.style.background = "#fff";
}
};
//添加新节点
num++;
var oLi = ateElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
看,上⾯是⽤事件委托的⽅式,新添加的⼦元素是带有事件效果的,我们可以发现,当⽤事件委托的时候,根本就不需要去遍历元素的⼦节点,只需要给⽗级元素添加事件就好了,其他的都是在js⾥⾯的执⾏,这样可以⼤⼤的减少dom操作,这才是事件委托的精髓所在。
总结:
那什么样的事件可以⽤事件委托,什么样的事件不可以⽤呢?
适合⽤事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例⼦,mousemove,每次都要计算它的位置,⾮常不好把控,在不如说focus,blur之类的,本⾝就没⽤冒泡的特性,⾃然就不能⽤事件委托了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论