深入理解事件委托(事件代理)以及取消冒泡和默认事件

2,641 阅读4分钟

概念

事件委托是指利用事件冒泡原理,只指定一个事件处理程序,使用这一个事件处理程序管理一系列的同类型事件。

为什么要用事件委托?

一般来说,操作DOM需要事件处理程序,所以我们直接写出对应的事件处理程序即可。但是如果需要操作很多同类型的DOM,比如说给很多li标签添加相同的事件处理程序呢。我们确实可以使用for循环来实现这个功能,但是这样的话我们相当于操作了N次DOM,严重影响了网页性能。事件委托其实本质上是性能优化的产物,或者说是性能优化的一种实践而已。它利用的正是尽量使用JS计算,最后一次性修改DOM,从而大大减少与DOM的交互,减少回流和重绘,提升网页性能。事件委托除了改善性能外,还有一点就是能够减少内存的占用。事件处理函数是对象,对象就会占用内存空间,如果有大量的事件处理函数,那么内存占用自然会很高,影响性能。使用事件委托的话,我们只需要对元素的父级指定事件处理函数,从而减少内存占用。

事件委托的原理

事件委托是依赖事件冒泡原理实现的。事件冒泡指的是事件会从最深的节点处开始逐步往上传播事件,由于有这样的机制,我们可以只给最外面的元素添加事件,当内层的标签触发了某个事件,该事件会通过事件冒泡传播到最外层标签,从而触发真正的事件处理函数。这也就是事件委托的过程,子元素委托父级元素代为执行事件。

<div id="parent">
  <div>111</div>
  <div>222</div>
  <div>333</div>
  <div>444</div>
</div>

// 使用事件代理
window.onload() = function(){
    var mydiv = document.getElementById("parent");
    mydiv.onclick = function(){
        console.log("hello");
    }
}

通过给父级元素添加事件实现了简单的事件代理,当点击子容器时,会通过事件冒泡原理传播到父级元素从而触发事件打印hello。但是这种代码有一个问题,那就是如果我点击父级元素,它同样也会触发事件打印hello,如果我不想这么做怎么办呢?接着看下面这段代码。

window.onload = function(){
    var mydiv = document.getElementById("parent");
    mydiv.onclick = function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() === "div"){
            console.log("hello");
            console.log(target.innerHTML);
        }
    }
}

这里我们给事件处理函数传递事件,从而可以使用事件所带有的属性target来捕获事件的目标节点,也就是当前事件所操作的DOM节点,即返回给我们子节点div。返回的是一个对象,我们为了判断,需要使用它的属性nodeName以及toLowerCase()方法转换为名字的小写。

总结一下,事件委托带给我们的好处主要在于性能优化方面,有以下几点。

  1. 可以节省内存开销,减少事件的注册
  2. 可以减少操作DOM元素,提升网页性能
  3. 可以方便地动态添加和修改元素,不需要因为元素的变化而修改绑定

另外需要注意的是事件委托并不适合所有事件,因为有的事件不能冒泡,比如blur,focus,load,unload等特殊事件。

阻止事件冒泡

W3C的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。 stopPropagation是事件的一个方法,作用是阻止目标元素的事件冒泡,但是不会组织默认行为。

window.event? window.event.cancelBubble = true : e.stopPropagation();

取消默认事件

W3C的方法是e.preventDefault(),IE则是使用e.returnValue = false。 preventDefault()是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为。既然是默认行为,那么元素必须有默认行为才能被取消,如果元素本身就没有默认行为,调用自然就无效了。什么元素有默认行为呢?如链接<a href="">,提交按钮<input type=”submit”>等,这些都有默认行为。

代码实现部分

阻止冒泡

function stopBubble(e) { 
//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //因此它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //否则,我们需要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}

阻止默认事件

function preventDefault(e){
    if(e && e.preventDefault){
        e.preventDefault();
    }
    else{
        window.event.returnValue = false;
    }
    return false;
}

JS中事件对象的注意要点

event对象代表事件的状态,例如触发event对象的元素、鼠标的位置及状态、按下的键等等。event对象只在事件发生的过程中才有效。firefox里的event跟IE里的不同,IE里的是全局变量,随时可用;firefox里的要用参数引导才能用,是运行时的临时变量。在IE/Opera中是window.event,在Firefox中是event;而触发事件的对象,在IE中是window.event.srcElement,在Firefox中是event.target,Opera中两者都可用。