JS 事件代理

事件处理器:onclick、onmouseover....html

在传统的事件处理中,你须要为每个元素添加或者是删除事件处理器。然而,事件处理器将有可能致使内存泄露或者是性能降低——你用得越多这种风险就越大。node

JavaScript事件代理:当咱们须要对不少元素添加事件的时候,能够经过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。这主要得益于浏览器的事件冒泡机制web

它是怎么运做的呢?

事件代理用到了两个特性:事件冒泡以及目标元素编程

事件冒泡当一个元素上的事件被触发的时候,好比说鼠标点击了一个按钮,一样的事件将会在那个元素的全部祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。浏览器

目标元素:任何一个事件的目标元素都是最开始的那个元素也就是咱们出发的元素。在咱们的这个例子中也就是按钮。ruby

使用事件代理:把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,而且能够得知这个事件是从哪一个元素开始的。app

用代码写出来是什么样呢?

咱们所要关心的只是如何检测目标元素而已。比方说咱们有一个table元素,ID是“report”,咱们为这个表格添加一个事件处理器以调用editCell函数。editCell函数须要判断传到table来的事件的目标元素。考虑到咱们要写的几个函数中都有可能用到这一功能,因此咱们把获取目标元素单独放到一个名为getEventTarget的函数中:框架

function getEventTarget(e) {
    //event属性兼容写法
    e = e || window.event;
    //获取目标元素兼容写法
    return e.target || e.srcElement;
}

 接下来就是editCell函数了,这个函数调用到了getEventTarget函数。一旦咱们获得了目标元素,剩下的事情就是看看它是不是咱们所须要的那个元素了。ide

function editCell(e) {
    var target = getEventTarget(e);
    if(target.tagName.toLowerCase() == 'td') {
         // DO SOMETHING WITH THE CELL
    }
}

在editCell函数中,咱们经过检查目标元素标签名称的方法来肯定它是不是一个表格的单元格。函数

事件冒泡及捕获

以前的介绍中已经说到了浏览器的事件冒泡机制。这里再详细介绍一下浏览器处理DOM事件的过程。对于事件的捕获和处理,不一样的浏览器厂商有不一样的处理机制,这里咱们主要介绍W3C对DOM2.0定义的标准事件。

DOM2.0模型将事件处理流程分为三个阶段:1、事件捕获阶段,2、事件目标阶段,3、事件起泡阶段。如图:

事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程当中,事件相应的监听函数是不会被触发的。

事件目标:当到达目标元素以后,执行目标元素该事件相应的处理函数。若是没有绑定监听函数,那就不执行。

事件起泡:从目标元素开始,往顶层元素传播。途中若是有节点绑定了相应的事件处理函数,这些函数都会被一次触发。若是想阻止事件起泡,可使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。

举个栗子?

假设有一个 UL 的父节点,包含了不少个 Li 的子节点:

<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>

咱们一般的作法是对每个元素进行循环操做添加监听事件:

var oUl=document.getElementById("parent-list");
var aLi=oUl.getElementsByTagName('li');

for (var i = 0; i < aLi.length; i++) {
    aLi[i].onclick=function () {
        console.log(this.id);
    }
}

若是这个UL中的Li子元素会频繁地添加或者删除,咱们就须要在每次添加Li添加事件处理函数,这就增长了复杂度和出错的可能性。

更简单的方法是使用事件代理机制,当事件被抛到更上层的父节点的时候,咱们经过检查事件的目标对象(target)来判断并获取事件源Li。下面的代码能够完成咱们想要的效果:

var oUl=document.getElementById("parent-list");
var aLi=oUl.getElementsByTagName('li');

function target(e) {
    var oEvent=e||event;
    return oEvent.target||oEvent.srcElement;
}

oUl.addEventListener('click',function (e) {
    var oEvent=e||event;
    var targets=target(oEvent);
    if (targets.tagName.toLowerCase()=='li') {
        console.log(targets.id);
    }
})

为父节点添加一个click事件,当子节点被点击的时候,click事件会从子节点开始向上冒泡。父节点捕获到事件以后,经过判断e.target.nodeName来判断是否为咱们须要处理的节点。而且经过e.target拿到了被点击的Li节点。从而能够获取到相应的信息,并做处理。

jQuery中delegate函数

下面看一下jQuery中提供的事件代理接口的使用方法。

$("#link-list").delegate("a", "click", function(){ // "$(this)" is the node that was clicked
  console.log("you clicked a link!",$(this)); });

jQuery的delegate的方法须要三个参数,一个选择器,一个事件名称,和事件处理函数。

优势、缺点

经过上面的介绍,你们应该可以体会到使用事件委托对于web应用程序带来的几个优势:

1.管理的函数变少了。不须要为每一个元素都添加监听函数。对于同一个父节点下面相似的子元素,能够经过委托给父元素的监听函数来处理事件。

2.能够方便地动态添加和修改元素,不须要由于元素的改动而修改事件绑定。

3.JavaScript和DOM节点之间的关联变少了,这样也就减小了因循环引用而带来的内存泄漏发生的几率。

潜在的问题也许并不那么明显,可是一旦你注意到这些问题,你就能够轻松地避免它们:

你的事件管理代码有成为性能瓶颈的风险,因此尽可能使它可以短小精悍。 

不是全部的事件都能冒泡的。blur、focus、load和unload不能像其它事件同样冒泡。事实上blur和focus能够用事件捕获而非事件冒泡的方法得到(在IE以外的其它浏览器中)。 

在管理鼠标事件的时候有些须要注意的地方。若是你的代码处理mousemove事件的话你赶上性能瓶颈的风险可就大了,由于mousemove事件触发很是频繁。而mouseout则由于其怪异的表现而变得很难用事件代理来管理。 

在JavaScript编程中使用代理

上面介绍的是对DOM事件处理时,利用浏览器冒泡机制为DOM元素添加事件代理。其实在纯JS编程中,咱们也可使用这样的编程模式,来建立代理对象来操做目标对象。这里引用司徒正美相关文章中的一个例子:

var delegate = function(client, clientMethod) { return function() { return clientMethod.apply(client, arguments); } } var ClassA = function() { var _color = "red"; return { getColor: function() { console.log("Color: " + _color); }, setColor: function(color) { _color = color; } }; }; var a = new ClassA(); a.getColor(); a.setColor("green"); a.getColor(); console.log("执行代理!"); var d = delegate(a, a.setColor); d("blue"); console.log("执行完毕!"); a.getColor();

上面的例子中,经过调用delegate()函数建立的代理函数d来操做对a的修改。这种方式尽管是使用了apply(call也能够)来实现了调用对象的转移,可是从编程模式上实现了对某些对象的隐藏,能够保护这些对象不被随便访问和修改。

在不少框架中都引用了委托这个概念用来指定方法的运行做用域。比较典型的如dojo.hitch(scope,method)和ExtJS的createDelegate(obj,args)。有兴趣的同窗能够看一下他们的源代码,主要也是js函数的apply方法来制定执行做用域。(其实我对于上面这个栗子,不是特别理解是如何运用的事件代理,可以深入理解的童鞋请帮我留言讲解一下,谢谢)

总结:

已经有一些使用主流类库的事件代理示例出现了,好比说jQuery、Prototype以及Yahoo! UI。你也能够找到那些不用任何类库的例子,好比说Usable Type blog上的这一个。一旦须要的话,事件代理将是你工具箱里的一件驾轻就熟的工具,并且它很容易实现。

 

本博客是转自两篇文章,博主结合本身的理解,整理出这篇博文,原文连接以下:

http://blog.csdn.net/weinideai/archive/2009/01/19/3835839.aspx

http://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html

相关文章
相关标签/搜索