JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。使用事件委托技术能让你避免对特定的每个节点添加事件监听器;相反,事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。
为什么要用事件委托:
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
事件委托的原理:
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
<ul id="parent-list"> <li id="post-1">Item 1</li> <li id="post-2">Item 2</li> <li id="post-3">Item 3</li> <li id="post-4">Item 4</li> <li id="post-5">Item 5</li> <li id="post-6">Item 6</li> </ul>
当每个子元素被点击时,将会有各自不同的事件发生。你可以给每个独立的li
元素添加事件监听器,但有时这些li
元素可能会被删除,可能会有新增,监听它们的新增或删除事件将会是一场噩梦,尤其是当你的监听事件的代码放在应用的另一个地方时。当子元素的事件冒泡到父ul
元素时,你可以检查事件对象的target属性,捕获真正被点击的节点元素的引用。简单:下面是一段很简单的JavaScript代码,演示了事件委托的过程:
// 找到父元素,添加监听器... document.getElementById("parent-list").addEventListener("click",function(e) { // e.target是被点击的元素! // 如果被点击的是li元素 if(e.target && e.target.nodeName == "LI") { // 找到目标,输出ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });
看下面例子,用一般方法让子节点实现相同功能
<ul id="ul1"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul>
实现功能是:点击li弹出123
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(123) } } }
用事件委托方式:
window.onload=function(){ var oUl = document.getElementById(‘ul1‘); oUl.onclick = function(e){ var e = e || window.event; var target = e.target || e.srcElement; if(target.nodeName.toLowerCase() == ‘li‘){ alert(123); alert(target.innerHTML); } } }
这样改下就只有点击li会触发事件了,且每次只执行一次dom操作,如果li数量很多的话,将大大减少dom的操作,优化的性能。
但是,上面是说li操作的同样的效果,要是每个li被点击的效果不一样,那么事件委托还有用吗?
一般方法:需要4次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 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(‘选择‘); } }
事件委托方法进行优化
window.onload = function(){ var oBox = document.getElementById(‘box‘); oBox.onclick= function(e){ var e = e || window.event var target = e.target || e.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; } } } }
如果是新增的节点,新增的节点会有事件吗?
<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子节点,用一般方法:
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.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‘ } } btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); } }
但是你会发现,新增的li是没有事件的,说明添加子节点的时候,事件没有一起添加进去,解决:将for循环用一个函数包起来,命名为mHover
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.getElementsByTagName(‘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(); btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); mHover(); } }
虽然功能实现了,看着还挺好,但实际上无疑是又增加了一个dom操作,在优化性能方面是不可取的,用事件委托可以优化:
window.onload = function(){ var btn = document.getElementById(‘btn‘); var oUl = document.getElementById(‘ul1‘); var aLi = document.getElementsByTagName(‘li‘); var num = 4; oUl.onmouseover = function(e){ var e = e || window.event var target = e.target || e.srcElement if(target.nodeName.toLocaleLowerCase()==‘li‘){ target.style.background = "red"; } } oUl.onmouseout = function(e){ var e = e || window.event var target = e.target || e.srcElement if(target.nodeName.toLocaleLowerCase()==‘li‘){ target.style.background = "#fff"; } } btn.onclick = function(){ num++; var oLi = document.createElement(‘li‘); oLi.innerHTML = 111*num; oUl.appendChild(oLi); } }
上面是用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。
总结:
那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。