一道前端面试题开始:现有一个包含三个li的无序列表ul,点击每一个li时alert里边的内容,如何实现?
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
首先想到的是:
var ndItem = document.getElementsByTagName('li');
for(var i=0;i<3;i++){
ndItem[i].addEventListener('click', function () {
alert(i);
});
}
那么如果li的数量变了,不是3个,变成300个了呢?如果继续通过上述方法,那么dom注册的监听事件就会变为原来的100倍,显然这样就不合适了,那么,事件委托 正好可以解决这个问题。
事件委托,又叫事件代理,Js高级 中这样描述:事件委托利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
(DOM事件流包括三个阶段:事件捕获阶段->处于目标阶段->事件冒泡阶段。
IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐渐向上传播到较为不具体的节点。
事件捕获:由不太具体的节点应该更早接收到事件,而具体的节点最后接收到事件。
)
用事件委托实现上边案例:
document.getElementById("list").addEventListener('click', function(e){
alert('点击内容是:' + e.target.innerText);
})
这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的,那么问题就来了,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发,接着往下看:
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们需要转成小写再做比较(习惯问题):
window.onload = function(){
var oUl = document.getElementById("list");
oUl.onclick = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
alert(target.innerHTML);
}
}
}
如上就只有点击li会触发事件了,且每次只执行一次dom操作,如果li数量很多的话,将大大减少dom的操作,优化的性能可想而知!
再比如说,我现在换了个题目:
<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>
也可以使用事件委托的方式:
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;
}
}
}
}
再再比如说,有下面这样的场景,可以手动添加新的li
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
初始方法:
window.onload = function(){
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 = '#fff';
}
}
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
如上虽然添加了新的标签,但是可以发现,事件没有添加上,那么可以通过事件委托来优化:
window.onload = function(){
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
var num = 4;
//事件委托,添加的子元素也有事件
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "red";
}
};
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "#fff";
}
};
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
最后,总结一下适用事件委托的事件包括:click、mousedown、mouseup、keydown、keyup和keypress。
值得注意的是:mouseover和mouseout事件也冒泡,但是要处理起来不太容易,而且经常需要计算元素的位置。