事件委托:
对'事件处理程序过多'问题的解决方案就是事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执 行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
举个例子:
子节点实现相同的功能:
<ul class="ul">
<li class="li">1</li>
<li class="li">2</li>
<li class="li">3</li>
<li class="li">4</li>
</ul>
实现功能是点击li,弹出1:
var oUl = document.getElementsByClassName('ul');
var oLi = document.getElementsByClassName('li');
for(var i = 0; i< oLi.length;i++){
oLi[i].onclick = function(){
alert(1);
}
}
var oUl = document.getElementsByClassName('ul')[0];
oUl.onclick=function(){
alert(1)
}
上面的代码的意思很简单,相信很多人都是这么实现的,我们看有多少次的dom操作,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li;
那么我们用事件委托的方式做又会怎么样呢?
var oUl = document.getElementsByClassName('ul')[0];
oUl.onclick=function(){
alert(1)
}
这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的,那么问题就来了,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发?
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名
var oUl = document.getElementsByClassName('ul')[0];
oUl.onclick=function(event){
var event = event||window.event;
var target = event.target || event.srcElement;
if(target.nodeName=='LI'){
alert(1);
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 oBox = document.getElementById('box');
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('选择');
}
优化后
var oBox = document.getElementById('box');
oBox.onclick = function(){
var event = event||window.event;
var target = event.target || event.srcElement;
if(target.nodeName == 'INPUT'){
switch(target.id){
case 'add':
alert('添加');
break;
case 'remove':
alert('删除');
break;
case 'move':
alert('移动');
break;
case 'select':
alert('选择');
break;
}
}
}
现在讲的都是document加载完成的现有dom节点下的操作,那么如果是新增的节点,新增的节点会有事件吗?
现在是移入li,li变红,移出li,li变白,这么一个效果,然后点击按钮,可以向ul中添加一个li子节点
<input type="button" name="" id="btn" value="添加" />
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
一般的做法
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('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 = 'white';
}
}
oBtn.onclick = function(){
num++;
var addLi = document.createElement('li');
addLi.innerHTML = 111*num;
oUl.appendChild(addLi)
}
但是你会发现,新增的li是没有事件的,说明添加子节点的时候,事件没有一起添加进去,这不是我们想要的结果,那怎么做呢?一般的解决方案会是这样,将for循环用一个函数包起来,在添加新街店的时候再调用它,这里就不演示了,但无疑又新加了一个dom操作
优化:
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName('li');
var num =4;
oUl.onmouseover = function(event){
var event = event||window.event;
var target = event.target||event.scrElement;
if(target.nodeName == 'LI'){
target.style.background = 'red';
}
}
oUl.onmouseout = function(event){
var event = event||window.event;
var target = event.target||event.scrElement;
if(target.nodeName == 'LI'){
target.style.background = 'white';
}
}
oBtn.onclick = function(){
num++;
var addLi = document.createElement('li');
addLi.innerHTML = 111*num;
oUl.appendChild(addLi)
}
上面是用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。