事件(五):事件委托
什么是事件委托:
高级程序设计里面:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
通俗的讲,事件就是onclick,onmouseover,onmouseout,等就是事件,委托呢,就是让别人来做,这个事件本来是加在某些元素上的,然而你却加到别人身上来做,完成这个事件。
也就是:利用冒泡的原理,把事件加到父级上,触发执行效果
优点:
1.不需要为每一个元素都添加监听事件而是通过委托给父元素来处理。这样就减少了内存,性能提高了
2.可以方便动态添加元素。不需要再为新添加的元素重新绑定事件。
我们可以看一个例子:需要触发每个li来改变他们的背景颜色
<ul id="ul"> <li>aaaaaaaa</li> <li>bbbbbbbb</li> <li>cccccccc</li> </ul>
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); for(var i=0; i<aLi.length; i++){ aLi[i].onmouseover = function(){ this.style.background = "red"; } aLi[i].onmouseout = function(){ this.style.background = ""; } } }
缺点:可能有很多个li用for循环的话就比较影响性能。
下面我们可以用事件委托的方式来实现这样的效果。html不变
window.onload = function(){ var oUl = document.getElementById('ul'); var aLi = oUl.getElementsByTagName('li'); /* * 这里要用到事件源:event对象,事件源,不管在哪个事件中,你操作的那个元素,就是事件源 * ie:window.event.srcElement * 标准下:event.target * nodeName: 找到元素的标签名 */ oUl.onmouseover = function(event){ debugger; var ev = event || window.event; var target = ev.target || ev.srcElement if(target.nodeName.toLowerCase() == 'li'){ target.style.background = 'red'; } } oUl.onmouseout = function(event){ debugger; var ev = event || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ target.style.background = ''; } } }
我们还拿这个例子看,但是我们要做动态的添加li。点击button动态添加li
<input type="button" id="btn" /> <ul id="ul"> <li>aaaaaaaa</li> <li>bbbbbbbb</li> <li>cccccccc</li> </ul>
不用事件委托我们会这样做:
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); var iNow = 4; for(var i=0; i<aLi.length; i++){ aLi[i].onmouseover = function(){ this.style.background = "red"; } aLi[i].onmouseout = function(){ this.style.background = ""; } } oBtn.onclick = function(){ iNow ++; var oLi = document.createElement("li"); oLi.innerHTML = 1111 *iNow; oUl.appendChild(oLi); } }
缺点:点击按钮新加的li上面没有鼠标移入事件来改变他们的背景颜色
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); var iNow = 4; oUl.onmouseover = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = "red"; } } oUl.onmouseout = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = ""; } } oBtn.onclick = function(){ iNow ++; var oLi = document.createElement("li"); oLi.innerHTML = 1111 *iNow; oUl.appendChild(oLi); } }
除了提高性能和节省内存的好处外,事件委托的另一个好处在于,页面动态变化后,不需要重新检索元素和绑定事件
上面的例子是说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>
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 (event) { var ev = event || 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; } } } }
用事件委托就可以只用一次dom操作就能完成所有的效果,比上面的性能肯定是要好一些的
由于事件处理程序可以为现代 Web 应用程序提供交互能力,因此许多开发人员会不分青红皂白地 向页面中添加大量的处理程序
在 JavaScript 中,添加到页面上 的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个 函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程 序而导致的 DOM 访问次数,会延迟整个页面的交互就绪时间
内存中留有那 些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的 主要原因。
两种情况下,可能会造成上述问题
1、从文档中移除带有事件处理程序的元素时。 这可能是通过纯粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法
2、更多地是发 生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除 了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收
<div id="myDiv"> <input type="button" value="Click Me" id="myBtn"> </div>
<script> var btn = document.getElementById("myBtn"); btn.onclick = function(){ //先执行某些操作 document.getElementById("myDiv").innerHTML = "Processing..."; //麻烦了! }; </script>
但问题在于,当按钮被从页面中移除时,它还带着一个 事件处理程序呢。在元素上设置 innerHTML 可以把按钮移走,但事件处理程序仍然与按钮保持 着引用关系。有的浏览器(尤其是 IE)在这种情况下不会作出恰当地处理,它们很有可能会将对元素和 对事件处理程序的引用都保存在内存中。如果你知道某个元素即将被移除,那么最好手工移除事件处理 程序
<script> var btn = document.getElementById("myBtn"); btn.onclick = function(){ //先执行某些操作 btn.onclick = null; //移除事件处理程序 document.getElementById("myDiv").innerHTML = "Processing..."; //麻烦了! }; </script>
在此,我们在设置div的 innerHTML 属性之前,先移除了按钮的事件处理程序。这样就确保了 内存可以被再次利用,而从 DOM 中移除按钮也做到了干净利索。
导致“空事件处理程序”的另一种情况,就是卸载页面的时候。毫不奇怪,IE8 及更早版本在这种 情况下依然是问题最多的浏览器,尽管其他浏览器或多或少也有类似的问题。如果在页面被卸载之前没 有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页 19 面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序 占用的内存并没有被释放。 一般来说,最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。 在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这 种类似撤销的操作,我们可以把它想象成:只要是通过 onload 事件处理程序添加的东西,最后都要通 过 onunload 事件处理程序将它们移除。
总结:
那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了
委托缺点:
- 事件委托基于冒泡,对于不冒泡的事件不支持。
- 层级过多,冒泡过程中,可能会被某层阻止掉。
- 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td。
- 把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。