jQuery事件编写进阶

jQuery事件编程进阶

事件委托,是一种优化DOM元素事件绑定的技巧,利用事件冒泡的原理,通过绑定事件到父元素,检查event触发元素的target,最终执行相应的事件函数处理,它的几个好处一般前端开发程序员都知道。在jQuery中,一般是delegate()方法和.live()方法,但是,如何选择事件委托的方法,或者在什么情况下用.live(),什么情况下用.delegate(),这个值得讲一讲:

live: function( types, data, fn ) {
      jQuery( this.context ).on( types, this.selector, data, fn );
      return this;
    }
delegate: function( selector, types, data, fn ) {
      return this.on( types, selector, data, fn );
}

查看这2个事件委托的源代码可知,live方法中,有个元素的执行环境,这个执行环境默认是document,所以,如果把live事件委托写在$(document).ready(function() {})之外,也是没有问题的。live()在某种情况下会引起性能问题,这主要包括2个方面:1.live()方法虽然避免了绑定事件处理到很多DOM元素,但是,它在一开始选择了文档中的所有元素,如果一个文档有很多的子节点,比如文档中的一个表有几十列,几百行表内容,而事件要绑定到这个表的某一行某一列,那么,live()方法在一开始选择这个表的所有行,所有列的时候,就是一个非常大的性能消耗,会导致脚本反应很迟钝。2.因为live()是绑定到document上面,所以会有大量的事件冒泡,事件冒泡要从嵌套最深的DOM元素往上一直冒到document上面,这样长路径的事件冒泡也是一个很昂贵的性能消耗。而采用.delegate(),事件绑定到$()函数的选择表达式元素上,因此页面的事件注册更加清晰,而事件冒泡更少。

由于初始化元素的选择和过度的事件冒泡,开发者们一般倾向于.delegate()方法,而摈弃.live()方法。但是,live()方法还是有其可用之处的,如果我们明智的使用它,过早的调用或者传递一个执行环境给它(即设置它的context),live()就会趋利避害,发挥它的优势所在。其中一个改善live()性能的方法是把live()移出$(document).ready()之外,加入live()事件注册的脚本是放在<head>结束标签之前,那么live()选择元素的工作就会非常少,因为那时,整个DOM还没有被加载注册,但document,这个live()的执行环境,却已经可用了。

(function($) {
   $('div.photo').live('mouseenter mouseleave',
    function(event) {
      var $details = $(this).find('.details');
      if (event.type == 'mouseenter') {
        $details.fadeTo('fast', 0.7);
       } else {
     $details.fadeOut('fast');
    }
});
})(jQuery);

由于我们不必等待整个DOM加载完毕,我们就可以立即确定mouseenter 和mouseleave 的事件行为将应用到<div>元素集上面,只要该元素集在页面中一被渲染显示就会起作用了。要理解这种事件注册技术的好处,我们可以想像一下要绑定一个事件处理函数来阻止一个链接元素的单击(click) 默认行为。如果我们直到整个DOM已经ready时,才绑定click事件注册函数,那我们就要冒着在click事件注册到那个链接元素之前,用户点击这个链接元素,从而导致浏览器离开当前页面,而没有采用ajax方式实现页面的无刷新来更新内容。Live()在早期注册,我们就有了事件早早绑定,却避免扫描整个DOM结构导致的性能消耗的好处。另一个使用.live()的技术是给它提供一个执行环境(context),类似于.delegate(),以此来减少事件冒泡。比如(‘div.photo’).live就改成$(‘div.photo’, $(‘#gallery’)[0]).live,这样的话,类似于.delegate(),$(‘div.photo’, $(‘#gallery’)[0]).live必须放在$(document).ready()函数里面,但这样也就失去了早期注册的那些好处。

在jQuery 1.9版本中,已经取消了live方法,但是这种在DOM加载早期,通过document事件委托绑定具体事件处理函数,避免jQuery扫描整个DOM结构导致的性能开销的编程思想确实有借鉴意义的。在jQuery 1.9中,上面用live的事件委托代码可以改写成:

(function($) {
   $(document).on('mouseenter mouseleave','div.photo'
    function(event) {
      var $details = $(this).find('.details');
      if (event.type == 'mouseenter') {
        $details.fadeTo('fast', 0.7);
       } else {
     $details.fadeOut('fast');
    }
});
})(jQuery);

如果把这段代码放在head结束标签之前,这样就不必等整个DOM加载完毕,通过document事件委托事先进行针对div.photo元素的处理的事件绑定,避免jQuery扫描整个DOM结构的性能开销。当然,div.photo元素最好是body标签的直接子元素,这样就可以减少事件冒泡的过程,如果div.photo在body下面嵌套比较深,则应该权衡一下是否在$(document).ready()采用delegate方法代替。

jQuery的特殊事件

jQuery的自定义事件,功能很强大,但是特殊事件跟自定义事件的结合使用,可以在框架层面来解决一些代码编写方面的问题,类似于java中的AOP切面编程。延迟事件执行,我们来看下面一段代码:

$(document).ready(function() {
  var timer = 0;
  $window.scroll(function() {
   if (!timer) {
   timer = setTimeout(function() {
   checkScrollPosition();
   timer = 0;
   }, 250);
  }
  }).scroll();
});

这里给窗体滚动事件添加处理函数,这个处理函数在窗体滚动时每次延迟250毫秒执行。如果不加延迟,浏览器哪怕滚动一个像素,都会触发checkScrollPosition()函数的调用,浏览器反复调用checkScrollPosition()函数,可能会引起性能方面的问题而导致浏览器进入“假死状态”,常见的窗体类似事件有scroll, resize, 和 mousemove,给这些窗体事件添加处理函数,希望都能添加延迟代码,使事件延迟执行,减少函数调用次数,从而提升用户体验,给用户比较平滑的浏览器效果体验。通过jQuery的特殊事件和自定义事件的结合,我们能够优化上面的事件延迟代码,从而移除事件注册中的setTimeout()函数,类似于java中的切面编程。这样事件绑定中只要直接调用checkScrollPosition()方法,不再需要在外面再包装setTimeout函数。请看以下示例:

(function($) {
$.event.special.throttledScroll = {
  setup: function(data) {
   var timer = 0;
   $(this).bind('scroll.throttledScroll', function(event) {
    if (!timer) {
    timer = setTimeout(function() {
     $(this).triggerHandler('throttledScroll');
     timer = 0;
     }, 250);
    }
   });
  },
  teardown: function() {
   $(this).unbind('scroll.throttledScroll');
  }
 };
})(jQuery);
$(document).ready(function() {
   $window.bind('throttledScroll', checkScrollPosition).trigger('throttledScroll');
});

这样最大程度的简化了事件绑定的代码,并且给了我们一个良好的可复用的事件延迟机制。因为除了窗体可以绑定throttledScroll事件,页面中有滚动条的其他div元素也可以绑定throttledScroll事件,而事件延迟写在$.event.special.throttledScroll的setup函数里面,这样DOM只选要关心具体事件处理函数的实现,不需要再手动添加延迟代码来改善性能。这样编写代码,结构良好,代码更清晰易懂,复用性也高。

20 responses

Leave a Reply

Your email address will not be published. Required fields are marked *

  1. sjp sjp says:

    $window.bind(‘throttledScroll’, checkScrollPosition).trigger(‘throttledScroll’);
    这句是不是写错了?

  2. Wordgold says:

    live和delegate过时了吧,现在都使用on和off

  3. Tiegen Zhutg says:

    这句没写错,$window.bind(‘throttledScroll’, checkScrollPosition)是绑定自定义事件throttledScroll,trigger(‘throttledScroll’)是触发刚刚绑定的throttledScroll,上面的代码总$(this).bind(‘scroll.throttledScroll’,function(event) {})是把throttledScroll作为scroll事件的一个别名。$window.bind(‘throttledScroll’其实底层绑定的是scroll事件。

  4. Tiegen Zhutg says:

    明白你的意思,但是我在这里介绍live和delegate,实际上是通过说两者的区别,来介绍一种通过document事件委托事先进行针对页面元素的事件处理绑定,避免jQuery扫描整个DOM结构的性能开销的思想。live 和delegate,底层还是调用了on,只是对on进行了包装而已。$(document ).on( types, this.selector, data, fn );来实现这种思想是一样的。如果有什么问题,欢迎继续提问。

  5. Yuanjianhang says:

    你好,$.event.special.throttledScroll这个event下的special对象是jquery 1.9对外提供的新功能吗?还有,$window.bind(‘throttledScroll’, checkScrollPosition).trigger(‘throttledScroll’);这样trigger之后就可以自动调用setup方法吗?

  6. Tiegen Zhutg says:

    $.event.special.throttledScroll这个event下的special对象,jquery 1.8就提供了。
    对的。$window.bind(‘throttledScroll’, checkScrollPosition).trigger(‘throttledScroll’);这样trigger之后就可以自动调用setup方法了。

  7. Tiegen Zhutg says:

    刚才上面的解释不太正确。在此补充纠正一下:$window.bind(‘throttledScroll’, checkScrollPosition)绑定后,会自动调用setup方法。setup方法是在bind的时候被调用的,trigger只是触发刚刚绑定的事件。trigger的时候,会每隔250ms调用一次checkScrollPosition函数,可能我写的代码示例:$window.bind(‘throttledScroll’, checkScrollPosition).trigger(‘throttledScroll’);绑定后又立即触发让人产生了误解,真实情况是,setup是bind的时候被调用,就像事件注册一样的,trigger只是用来触发绑定的事件。

  8. 运动服 says:

    评论框都升级了 越来越先进

  9. 灯盏细辛 says:

    很好!受启发!感谢!

  10. […] 事件委托。事件委托是建立在冒泡机制之上的,我们可以把这想象成潜水,你不必冒着高压潜到水底去找到那个小泡泡,可以在水面上控制它浮上来的大泡泡,这样可以明显的提高效率,擒贼先擒王也是这个道理,挟天子以令诸侯亦是这个道理。关于事件委托,前两天在阿里巴巴ued看到了一篇好文,jquery事件编写进阶,墙裂建议围观。 […]

  11. 蓝海宏图营销策划 says:

    不是很了解的,主要还是少许的内容

  12. 羊小闲 says:

    不错!学到了!

  13. wade says:

    有空讲讲jquery.specialEvent啊

  14. 谢亮 says:

    对于 scroll, resize 这样怎样呢? http://bbs.jq-school.com/showtopic.aspx?topicid=477

  15. Charlie says:

    不错哈

  16. Mark Ma says:

    (function($) {

    $(document).on(‘mouseenter mouseleave’,’.head’

    function(event) {

    var $details = $(this).find(‘.details’);

    if (event.type == ‘mouseenter’) {

    $details.fadeTo(‘fast’, 0.9);

    } else {

    $details.fadeOut(‘fast’);

    }

    });

    })(jQuery);

    这段代码为何要闭包?

  17. Carlos says:

    谢谢,目前遇到了dataGrid 渲染性能问题,主要是live引起的,刚好看到了你的文章,豁然开朗

  18. 卢林 says:

    $(document).on(‘mouseenter mouseleave’,’div.photo’, function(event) {});少了一个逗号。。。博主加上吧。