一道前端面试题开始:现有一个包含三个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事件也冒泡,但是要处理起来不太容易,而且经常需要计算元素的位置。