首页 > Web开发, 挨踢(IT) > 实例分析JavaScript中的事件委托和事件绑定

实例分析JavaScript中的事件委托和事件绑定

2012年10月21日 发表评论 阅读评论 8,043 人阅读    

  我们在学习JavaScript中,难免都会去网上查一些资料。也许偶尔就会遇到“事件委托”(也有的称我“事件代理”,这里不评论谁是谁非。以下全部称为“事件委托”),尤其是在查JavaScript的事件处理的时候。但是,大多数时说的是“事件绑定”,对于“事件委托”,或是不提,或是浅尝辄止。对于我这个比较好奇的人来说,实在很蛋疼。尤其是想更多的了解“事件委托”的时候。

  这次干脆一劳永逸,自己把查出来的资料整理成一篇日志,总结这块的知识,也方便需要的朋友查阅。

JavaScript中事件传播过程那些事儿

  早期的web开发,浏览器厂商很难回答一个哲学上的问题:当你在页面上的一个区域点击时,你真正感兴趣的是哪个元素。这个问题带来了交互的定义。在一个元素的界限内点击,显得有点含糊。毕竟,在一个元素上的点击同时也发生在另一个元素的界限内。例如单击一个按钮。你实际上点击了按钮区域、body元素的区域以及html元素的区域。

  伴随着这个问题,两种主流的浏览器Netscape和IE有不同的解决方案。Netscape定义了一种叫做事件捕获的处理方法,事件首先发生在DOM树的最高层对象(document)然后往最深层的元素传播。在图例中,事件捕获首先发生在document上,然后是html元素,body元素,最后是button元素。

  IE的处理方法正好相反。他们定义了一种叫事件冒泡的方法。事件冒泡认为事件促发的最深层元素首先接收事件。然后是它的父元素,依次向上,知道document对象最终接收到事件。尽管相对于html元素来说,document没有独立的视觉表现,他仍然是html元素的父元素并且事件能冒泡到document元素。所以图例中噢噢那个button元素先接收事件,然后是body、html最后是document。如下图:

JavaScript事件冒泡和事件捕捉

图1-JavaScript时间冒泡和捕捉

 

事件冒泡:

  什么是“事件冒泡”呢?假设这里有一杯水,水被用某种神奇的方式分成不同颜色的几层。这时,从最底层冒出了一个气泡,气泡会一层一层地上升,直到最顶层。而你不管在水的哪一层观察都可以看到并捕捉到这个气泡。好了,把“水”改成“DOM”,把“气泡”改成“事件”。这就是“事件冒泡”。

  气泡带上了某种信息,会告诉其经过的每一层自己是在哪一层产生的。JavaScript的事件确实会带着这个属性。当程序捕获一个事件的时候,它会知道这个事件来自于页面上哪个元素。 事件委托也就是利用这个原理。

事件委托和事件绑定的简介

  事件绑定的介绍,网上比比皆是,大家也比较熟悉,D瓜哥文笔不好,就不献丑了。

  下面我们来重点介绍一下“事件委托”。其实,“事件委托”的概念很简单,生活中也不乏这样的例子。比如,有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。

事件委托和事件绑定的代码实现

  事件绑定的代码,网上一抓一把。这里只给出一个简单代码。诸如:

document.getElementById("id").onclick=function(){
   //这里是事件处理代码
}

  D瓜哥在第一次注意到事件委托的时候,明白这个大概意思。不过,陆游他老人家有句话说的好,“纸上得来终觉浅,绝知此事要躬行。”英语有一个很“心领神会”的一句话,是世界著名计算机专家Donald Knuth说的,”An algorithm must be seen to be believe!“D瓜哥从第一次注意到“事件委托”后,就考虑用代码如何实现。相信,这也是很多人第一次注意到“事件委托”的想法吧。哈哈

  这里给大家一个简要的示例。示例如下:

//document.onclick,从这点就能看出,这个示例把事件委托放到了document上。
document.onclick = function(event){
    //IE doesn't pass in the event object
    event = event || window.event;
    
    //IE uses srcElement as the target
    var target = event.target || event.srcElement;
    
    switch(target.id){
        case "help-btn":
                openHelp();
                break;
        case "save-btn":
                saveDocument();
                break;
        case "undo-btn":
                undoChanges();
                break;
        //如果有其元素需要处理点击事件,
        //只需要在这里添加不同的case分支就行。
    }
};

  当然,这里代码为了说明”事件委托”,代码尽可能简化了。不能用于工业生产。另外,为了写例子的方便和内容需要,一下的代码使用jQuery框架来实现相关代码。

  我们了解过了如何使用代码来实现“事件委托”和“事件绑定”了。下面我们从“处理速度”,“新增元素的处理”和“内存消耗”三方面来论述一下这两者的区分。

事件委托和事件绑定的处理速度对比

  先上段代码,让大家体验一下两种事件处理在速度上的巨大差别。业务逻辑很简单:点击按钮,先生成对应数量的元素,然后再分别利用“事件绑定”和“事件委托”各个生成的元素增加事件处理。

  通过上面的测试,可以看出,在元素数量比较大的时候,“事件委托”的效率灰常明显的比“事件绑定”好好的太多太多了(随着元素数量的增加,两者对比实在不成比例,用数量级差别以不足以形容了。哈哈)。在“高性能JavaScript”中,我提出来一些提高JavaSctipt性能的准则。“尽量使用‘事件委托’进行事件处理”也应该做为一条高性能JavaScript准则,加入进去。

事件委托和事件绑定针对新增元素的事件处理的对比

  在一些页面中,难免有时会增加一些新的“元素”。针对这些新增加的元素,事件绑定使如何处理的呢?事件委托又会如何呢?下面,我们还是使用实例来说话。代码如下:

  大家可以点击“点击新增元素”,来新增元素,然后再点击新增出来的元素,看看效果如何?

  大家也看到了,对于“事件绑定”中的新元素,并没有添加事件处理;但是,对于“事件委托”中的新元素,每个新元素都有事件处理。这是为啥相信大家都很明白?这是因为“事件绑定”是针对每个元素进行事件绑定处理,但是新的元素并没有进行事件绑定处理;而“事件委托”,是针对某个选择器下的某种元素,都进行事件处理。所以,只需要这些元素符合这个条件,都会进行事件处理。

事件委托和事件绑定的占用内存对比

  如果一个整体页面里有大量的需要绑定事件的元素,每个元素都要绑定一个函数,而每个函数都是对象,对象就会占用很多内存,内存中的对象越多,性能就越差。

  而事件委托,只在绑定事件的上级元素上绑定函数,次数十分有限(一般每次委托只有一次);另外,在事件被触发时,才会取出触发事件的元素来进一步处理。总体来讲,非常节约内存。

  我们来用实际例子来对比一下。还是利用上面速度测试中的例子,URL如下:
事件绑内存消耗测试URL:http://jsfiddle.net/DiGuaGe/MbYy6/2/embedded/result/
事件委托内存消耗测试URL:http://jsfiddle.net/DiGuaGe/6d7J6/1/embedded/result/
创建元素内存消耗测试URL:http://jsfiddle.net/DiGuaGe/UCXbM/embedded/result/
用IE浏览器打开,然后再打开“Windows任务管理器”,选中“进程”Tab页,注意观察“iexplore.exe”(这个就是IE浏览器的进程),可以看出内存消耗。

  在我的笔记本上(硬件,Dell E6410;Core i5;2G内存;软件:IE8)测试结果差距灰常大。具体对比如下:

JavaScript事件委托和事件绑定的内存消耗对比

图2-JavaScript事件委托和事件绑定的内存消耗对比”

  D瓜哥从“处理速度”、“新增元素事件处理”和“内存消耗”三方面比较了“事件委托”和“事件绑定”的对比,可以很容易看出,“事件委托”在“处理速度”和“内存消耗”上,有得天独厚的优势。所以,在Web编程的时候,尤其在构建大型系统的时候,应该尽量考虑使用“事件委托”。但是,“事件委托”并不是万能的;它也有一些弊端。下面我们在论述一下它的弊端。

使用“事件委托”时,需要注意的问题

  使用“事件委托”时,并不是说把事件委托给的元素越靠近顶层就越好。事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时。

  这点也可以从jQuery .live()方法的数次升级中看出一些“端倪”。

  jQuery 1.3新增了.live()方法来进行事件处理。刚开始时 .live()方法会把click事件绑定到$(document)对象。代码如下:

 $("#info_table td").live("click",function(){/*事件处理*/}); 
  

  默认把事件绑定到$(document)元素,如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。

  为了避免事件冒泡造成的性能损失,jQuery从1.4开始支持在使用.live()方法时配合使用一个上下文参数。代码如下:

$("td",$("#info_table")[0]).live("click",function(){/*事件处理*/});
  

  这样,”受托方”就从默认的$(document)变成了$(“#infotable”)[0],节省了冒泡的旅程。不过,与.live()共同使用的上下文参数必须是一个单独的DOM元素,所以这里指定上下文对象时使用的是$(“#infotable”)[0],即使用数组的索引操作符来取得的一个DOM元素。

  再说说一点。为了解决无谓生成元素集合的问题,jQuery 1.4.2干脆直接引入了一个新方法.delegate()。使用.delegate(),前面的例子可以这样写:

 $("#info_table td").live("click",function(){/*事件处理*/}); 
  

  《重构与模式》中有一句非常经典的话:“如果想成为一名更优秀的软件设计师,了解优秀软件设计的演变过程比学习优秀设计本身更有价值,因为设计的演变过程中隐藏着大智慧。”看来jQuery的live方法的演变,感觉用这句话说这个演变过程是如此的贴切。所以,我们在学习中,也许从一门技术的演变中,得到灵感,创造出更好的技术!

  这篇文章,参考了很多资料。这里表示感谢。另外,大家如果有什么好的见解,欢迎留言进行讨论。

注:

  关于更多的jQuery的介绍,请阅读参考资料中《jQuery代码优化:事件委托篇》。

参考资料:

  1. jQuery代码优化:事件委托篇
  2. JavaScript中的事件委托
  3. JavaScript事件冒泡和事件委托
  4. javascript 事件委托
  5. JavaScript事件委托优化
  6. JavaScript跨浏览器的添加删除事件绑定函数


作 者: D瓜哥,https://www.diguage.com/
原文链接:https://wordpress.diguage.com/archives/71.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

  1. 2012年10月22日10:19 | #1

    写的不错,希望继续努力。“如果想成为一名更优秀的软件设计师,了解优秀软件设计的演变过程比学习优秀设计本身更有价值,因为设计的演变过程中隐藏着大智慧。”,真是太好了,让为兄我恍然大悟,如拨云见日,茅塞顿开啊。

  2. D瓜哥
    2012年10月22日12:42 | #2

    原文中,把“‘事件委托’的效率灰常明显的比‘事件绑定’好”错写成“‘事件委托’的效率灰常明显的比‘事件委托’好”。现已改正。谢谢网友“上海┊卡卡西”的指正!如发现其他问题,请留言。D瓜哥先谢谢了。

  3. 成平
    2012年10月22日14:56 | #3

    我看了上述内容,其实核心就是attachEvent (ie) 和 addEventListener (firefox)
    至于提高效率,我倒觉得是绑定事件方式问题, 你可以把普通的绑定事件demo换成attachEvent 的绑定方式在试下效率。

    个人见解, 相互学习 ^_^

    • D瓜哥
      2012年10月22日23:05 | #4

      这个倒是没有注意到,我回来试试,然后再讨论。呵呵

  4. 2013年4月7日10:03 | #5

    看了博主写的明白了很多

  5. ZA
    2020年6月11日10:02 | #6

    瓜哥,有些图片怎么看不到

  1. 2013年4月10日23:42 | #1
  2. 2016年8月21日05:11 | #2