开发者社区> 行者武松> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

浅析JavaScript的事件代理和委托

简介:
+关注继续查看


在javasript中delegate这个词经常出现,看字面的意思,代理、委托。那么它究竟在什么样的情况下使用?它的原理又是什么?在各种框架中,也经常能看到delegate相关的接口。这些接口又有什么特殊的用法呢?这篇文章就主要介绍一下javascript delegate的用法和原理,以及Dojo,jQuery等框架中delegate的接口。

JavaScript事件代理

首先介绍一下JavaScript的事件代理。事件代理在JS世界中一个非常有用也很有趣的功能。当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。这主要得益于浏览器的事件冒泡机制,后面会详细介绍。下面我们具体举个例子来解释如何使用这个特性。这个例子主要取自David Walsh的相关文章(How JavaScript Event Delegation Works)。

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


  1. <ul id="parent-list"
  2.   <li id="post-1">Item 1</li> 
  3.   <li id="post-2">Item 2</li> 
  4.   <li id="post-3">Item 3</li> 
  5.   <li id="post-4">Item 4</li> 
  6.   <li id="post-5">Item 5</li> 
  7.   <li id="post-6">Item 6</li> 
  8. </ul>  

当我们的鼠标移到Li上的时候,需要获取此Li的相关信息并飘出悬浮窗以显示详细信息,或者当某个Li被点击的时候需要触发相应的处理事件。我们通常的写法,是为每个Li都添加一些类似onMouseOver或者onClick之类的事件监听。


  1. function addListeners4Li(liNode){ 
  2.     liNode.onclick = function clickHandler(){...}; 
  3.     liNode.onmouseover = function mouseOverHandler(){...} 
  4.  
  5. window.onload = function(){ 
  6.     var ulNode = document.getElementById("parent-list"); 
  7.     var liNodes = ulNode.getElementByTagName("Li"); 
  8.     for(var i=0, l = liNodes.length; i < l; i++){ 
  9.         addListeners4Li(liNodes[i]); 
  10.     }    
  11. }  

如果这个UL中的Li子元素会频繁地添加或者删除,我们就需要在每次添加Li的时候都调用这个addListeners4Li方法来为每个Li节点添加事件处理函数。这就添加的复杂度和出错的可能性。

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


  1. // 获取父节点,并为它添加一个click事件 
  2. document.getElementById("parent-list").addEventListener("click",function(e) { 
  3.   // 检查事件源e.targe是否为Li 
  4.   if(e.target && e.target.nodeName.toUpperCase == "LI") { 
  5.     // 真正的处理过程在这里 
  6.     console.log("List item ",e.target.id.replace("post-")," was clicked!"); 
  7.   } 
  8. });  

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

事件冒泡及捕获

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

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

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

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

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

jQuery和Dojo中delegate函数

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


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

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

而Dojo的与jQuery相似,仅是两者的编程风格上的差别:


  1. require(["dojo/query","dojox/NodeList/delegate"], function(query,delegate){ 
  2.  
  3.     query("#link-list").delegate("a","onclick",function(event) { 
  4.     // "this.node" is the node that was clicked 
  5.     console.log("you clicked a link!",this); 
  6.   }); 
  7. })  

Dojo的delegate模块在dojox.NodeList中,提供的接口与jQuery一样,参数也相同。

优点通过上面的介绍,大家应该能够体会到使用事件委托对于web应用程序带来的几个优点:

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

2.可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。

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

写到这里,突然想起了之前对于Dojo DataGrid的困惑:那么多的rows和cells,如何处理他们事件之间的关系。现在想想,使用委托就很简单了。所有的事件委托到grid最外层的节点上,当事件发生的时候通过一些方法来获取和添加事件的额外属性,如rowIndex, cellIndex,之后在分配到onRowClick,onCellClick之类的处理函数上。

在JavaScript编程中使用代理

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


  1. var delegate = function(client, clientMethod) { 
  2.         return function() { 
  3.             return clientMethod.apply(client, arguments); 
  4.         } 
  5.     } 
  6.     var ClassA = function() { 
  7.         var _color = "red"
  8.         return { 
  9.             getColor: function() { 
  10.                 console.log("Color: " + _color); 
  11.             }, 
  12.             setColor: function(color) { 
  13.                 _color = color; 
  14.             } 
  15.         }; 
  16.     }; 
  17.  
  18.     var a = new ClassA(); 
  19.     a.getColor(); 
  20.     a.setColor("green"); 
  21.     a.getColor(); 
  22.     console.log("执行代理!"); 
  23.     var d = delegate(a, a.setColor); 
  24.     d("blue"); 
  25.     console.log("执行完毕!"); 
  26.     a.getColor();  

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

在很多框架中都引用了委托这个概念用来指定方法的运行作用域。比较典型的如dojo.hitch(scope,method)和ExtJS的createDelegate(obj,args)。有兴趣的同学可以看一下他们的源代码,主要也是js函数的apply方法来制定执行作用域。


作者:puhongru

来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Javascript 事件propagation机制
Javascript 事件propagation机制
42 0
Javascript 事件propagation机制
Javascript 事件propagation机制
48 0
js实现禁止网页缩放(Ctrl+鼠标、+、-缩放有效亲测)
js实现禁止网页缩放(Ctrl+鼠标、+、-缩放有效亲测)背景:现在是凌晨1:35我还在帮客户修改网页,要求不高但来钱快,学生党不容易啊,客户提到了很多网页的优化,其中这一条 让我头疼了许久,大家就不用踩坑了,看下面的就可以了...
12 0
JS实现百叶窗特效
👀每天实习一个小案例,编程能力才会突飞猛进。
14 0
JS使用canvas实现(下雨天)特效
今天利用canvas实现一个简单的下雨天的网页案例!
13 0
js:实现一个类似Python的range函数生成数字序列
js:实现一个类似Python的range函数生成数字序列
30 0
JavaScript实现的网页放大镜效果
今天在观看视频学习的时候,学到了一个小技巧。就拿过来与大家进行分享一下啦。 实现的原理 分析需求:需要两张图,一大一小。然后根据鼠标的动作显示出不同的区域块的图像。 核心:鼠标事件的获取和处理。
1249 0
用JavaScript实现网页动态水印
原文:用JavaScript实现网页动态水印 1.基本原理 页面加载后,通过javascript创建页面元素div,并在div元素中创建文本节点,展示水印内容 设置div元素样式,将其zIndex设置一个较高的值,并设置透明度,实现浮在页面的水印效果 其核心逻辑如下所示 var mask_div = document.
1053 0
Javascript实现网页上的多级菜单(竖着)
使用方法:1,包含menu.js和menu.css 2,用创建主菜单类   main = new MainMenu(10,50,"My Menu");这里,MainMenu的前两个参数表示其左上角位置坐标,前者为left,后者为top。第三个参数为菜单标题。之后用MenuItem类建立菜单项MenuItem的源型如下:   function MenuItem(_parent,_caption
1098 0
+关注
行者武松
杀人者,打虎武松也。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Javascript异步编程
立即下载
Javascript中的对象
立即下载
Javascript中的函数
立即下载