JavaScript事件代理和委托详解


Posted in Javascript onApril 08, 2016

在javasript中,代理、委托经常出现。

那么它究竟在什么样的情况下使用?它的原理又是什么?

这里介绍一下javascript delegate的用法和原理,以及Dojo,jQuery等框架中delegate的接口。

JavaScript事件代理
事件代理在JS世界中一个非常有用也很有趣的功能。当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。

这主要得益于浏览器的事件冒泡机制,下面我们具体举个例子来解释如何使用这个特性。

这个例子主要取自David Walsh的相关文章(How JavaScript Event Delegation Works)。

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

<ul id="list">
 <li id="li-1">Li 1</li>
 <li id="li-2">Li 2</li>
 <li id="li-3">Li 3</li>
 <li id="li-4">Li 4</li>
 <li id="li-5">Li 5</li> 
</ul>

当我们的鼠标移到Li上的时候,需要获取此Li的相关信息并飘出悬浮窗以显示详细信息,或者当某个Li被点击的时候需要触发相应的处理事件。

我们通常的写法,是为每个Li都添加一些类似onMouseOver或者onClick之类的事件监听。

function addListenersLi(liElement) {
  liElement.onclick = function clickHandler() {
   //TODO
  };
  liElement.onmouseover = function mouseOverHandler() {
   //TODO
  }
 }

 window.onload = function() {
  var ulElement = document.getElementById("list");
  var liElements = ulElement.getElementByTagName("Li");
   for (var i = liElements.length - 1; i >= 0; i--) {
    addListenersLi(liElements[i]);
   } 
 }

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

这会造成添加或者删除过程的复杂度和出错的可能性。

解决问题方法是使用事件代理机制,当事件被抛到更上层的父节点的时候,我们通过检查事件的目标对象(target)来判断并获取事件源Li。

下面的代码可以完成想要的效果: 

/ 获取父节点,并为它添加一个click事件
document.getElementById("list").addEventListener("click",function(e) {
 // 检查事件源e.targe是否为Li
 if(e.target && e.target.nodeName.toUpperCase == "LI") {
 // 
 //TODO
 console.log("List item ",e.target.id," was clicked!");
 }
});

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

事件冒泡及捕获
浏览器的事件冒泡机制,对于事件的捕获和处理,不同的浏览器厂商有不同的处理机制,这里介绍W3C对DOM2.0定义的标准事件。

DOM2.0模型将事件处理流程分为三个阶段:

一、事件捕获阶段,

二、事件目标阶段,

三、事件起泡阶段。

如下图:

JavaScript事件代理和委托详解

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

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

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

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

jQuery:

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

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

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

require(["dojo/query","dojox/NodeList/delegate"], function(query,delegate){

 query("#list").delegate("li","onclick",function(event) {
 // "this.node" is the node that was clicked
 console.log("you clicked a link!",this);
 });
})

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

通过委托, 能够体会到使用事件委托对于开发带来的几个好处:

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

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

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

在JavaScript编程中使用代理
上面介绍的是对DOM事件处理时,利用浏览器冒泡机制为DOM元素添加事件代理。其实在纯JS编程中,我们也可以使用这样的编程模式,来创建代理对象来操作目标对象.

var delegate = function(client, clientMethod) {
  return function() {
   return clientMethod.apply(client, arguments);
  }
 }
 var Apple= function() {
  var _color = "red";
  return {
   getColor: function() {
    console.log("Color: " + _color);
   },
   setColor: function(color) {
    _color = color;
   }
  };
 };

 var a = new Apple();
 var b = new Apple();
 a.getColor();
 a.setColor("green");
 a.getColor();
 //调用代理
 var d = delegate(a, a.setColor);
 d("blue");
 //执行代理
 a.getColor();
 //b.getColor();

上面的例子中,通过调用delegate()函数创建的代理函数d来操作对a的修改。

这种方式尽管是使用了apply(call也可以)来实现了调用对象的转移,但是从编程模式上实现了对某些对象的隐藏,可以保护这些对象不被随便访问和修改。

在很多框架中都引用了委托这个概念用来指定方法的运行作用域。

比较典型的如dojo.hitch(scope,method)和ExtJS的createDelegate(obj,args)。

以上就是本文的全部内容,希望对大家学习javascript程序设计有所帮助。

Javascript 相关文章推荐
JavaScript和ActionScript的交互实现代码
Aug 01 Javascript
体验js中splice()的强大(插入、删除或替换数组的元素)
Jan 16 Javascript
js动态为代码着色显示行号
May 29 Javascript
JavaScript获取/更改文本框的值的实例代码
Aug 02 Javascript
ECMA5数组的新增方法有哪些及forEach()模仿实现
Nov 03 Javascript
Bootstrap CSS布局之表格
Dec 17 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
Jan 04 Javascript
js 去掉字符串前后空格实现代码集合
Mar 25 Javascript
javascript系统时间设置操作示例
Jun 17 Javascript
jQuery-Citys省市区三级菜单联动插件使用详解
Jul 26 jQuery
Vue修改项目启动端口号方法
Nov 07 Javascript
ant design的table组件实现全选功能以及自定义分页
Nov 17 Javascript
javascript高级选择器querySelector和querySelectorAll全面解析
Apr 07 #Javascript
关于cookie的初识和运用(js和jq)
Apr 07 #Javascript
纯js实现瀑布流布局及ajax动态新增数据
Apr 07 #Javascript
原生JavaScript实现Ajax的方法
Apr 07 #Javascript
JavaScript数据推送Comet技术详解
Apr 07 #Javascript
js实现商品抛物线加入购物车特效
Nov 18 #Javascript
js类式继承与原型式继承详解
Apr 07 #Javascript
You might like
PHP批量检测并去除文件BOM头代码实例
2014/05/08 PHP
php中json_encode处理gbk与gb2312中文乱码问题的解决方法
2014/07/10 PHP
访问编码后的中文URL返回404错误的解决方法
2014/08/20 PHP
PHP查找与搜索数组元素方法总结
2015/06/12 PHP
PHP 命名空间和自动加载原理与用法实例分析
2020/04/29 PHP
基于PHP实现解密或加密Cloudflar邮箱保护
2020/06/24 PHP
Jquery 常用方法经典总结
2010/01/28 Javascript
js生成动态表格并为每个单元格添加单击事件的方法
2014/04/14 Javascript
JQuery性能优化的几点建议
2014/05/14 Javascript
javascript工厂方式定义对象
2014/12/26 Javascript
JS+CSS实现分类动态选择及移动功能效果代码
2015/10/19 Javascript
实例讲解jQuery EasyUI tree中state属性慎用
2016/04/01 Javascript
JS封装的自动创建表格的实现代码
2016/06/15 Javascript
详解Angular中$cacheFactory缓存的使用
2016/08/19 Javascript
jQuery Easyui Treegrid实现显示checkbox功能
2017/08/08 jQuery
vue对storejs获取的数据进行处理时遇到的几种问题小结
2018/03/20 Javascript
浅谈在react中如何实现扫码枪输入
2018/07/04 Javascript
小程序scroll-view组件实现滚动的示例代码
2018/09/20 Javascript
百度小程序自定义通用toast组件
2019/07/17 Javascript
vue+vant实现商品列表批量倒计时功能
2020/01/13 Javascript
基于JS实现操作成功之后自动跳转页面
2020/09/25 Javascript
[48:54]VGJ.T vs infamous Supermajor小组赛D组败者组第一轮 BO3 第二场 6.3
2018/06/04 DOTA
python实现向ppt文件里插入新幻灯片页面的方法
2015/04/28 Python
详解在Python程序中解析并修改XML内容的方法
2015/11/16 Python
Python 模拟购物车的实例讲解
2017/09/11 Python
代码讲解Python对Windows服务进行监控
2018/02/11 Python
使用Python搭建虚拟环境的配置方法
2018/02/28 Python
对python 读取线的shp文件实例详解
2018/12/22 Python
Python OpenCV去除字母后面的杂线操作
2020/07/05 Python
大学生职业规划前言模板
2013/12/27 职场文书
环境卫生标语
2014/06/09 职场文书
会计专业应届生自荐信
2014/06/28 职场文书
房产授权委托书范本
2014/09/22 职场文书
2014年环境卫生工作总结
2014/11/24 职场文书
财务总监岗位职责范本
2015/04/03 职场文书
2016应届毕业生实习心得体会
2015/10/09 职场文书