【问题标题】:How to order events bound with jQuery如何对与 jQuery 绑定的事件进行排序
【发布时间】:2010-09-22 08:38:23
【问题描述】:

假设我有一个 Web 应用程序,它的页面可能包含 4 个脚本块 - 我编写的脚本可能会在其中一个块中找到,但我不知道哪个是由控制器处理的。

我将一些onclick 事件绑定到一个按钮,但我发现它们有时会以我没想到的顺序执行。

有没有办法确保秩序,或者你过去是如何处理这个问题的?

【问题讨论】:

  • 将您的回调添加到 CallBack 对象,当您想要触发它们时,您可以一次触发它们,它们将按照添加的顺序运行。 api.jquery.com/jQuery.Callbacks

标签: javascript jquery events


【解决方案1】:

如果顺序很重要,您可以创建自己的事件并绑定回调以在这些事件被其他回调触发时触发。

$('#mydiv').click(function(e) {
    // maniplate #mydiv ...
    $('#mydiv').trigger('mydiv-manipulated');
});

$('#mydiv').bind('mydiv-manipulated', function(e) {
    // do more stuff now that #mydiv has been manipulated
    return;
});

至少是这样的。

【讨论】:

  • 如果我们想停止在绑定代码之前的某个时间点定义的回调的点击事件的传播。
  • 我能问一下为什么这不被接受吗?目前接受的答案将我的答案列为最佳方法,所以我有点困惑。 :-)
  • 这适用于 mkoryak 的情况,但如果同一个事件被多次调用则不行。我有一个类似的问题,一次击键触发 $(window).scroll() 多次,按 reverse 顺序。
【解决方案2】:

如果您的所有回调始终存在并且您对它们相互依赖感到满意,Dowski 的方法就很好。

但是,如果您希望回调彼此独立,则可以利用冒泡并将后续事件作为委托附加到父元素。父元素上的处理程序将在元素上的处理程序之后触发,一直持续到文档。这非常好,因为您可以使用event.stopPropagation()event.preventDefault() 等来跳过处理程序并取消或取消取消操作。

$( '#mybutton' ).click( function(e) { 
    // Do stuff first
} );

$( '#mybutton' ).click( function(e) { 
    // Do other stuff first
} );

$( document ).delegate( '#mybutton', 'click', function(e) {
    // Do stuff last
} );

或者,如果您不喜欢这样,您可以使用 Nick Leaches bindLast 插件来强制最后绑定事件:https://github.com/nickyleach/jQuery.bindLast

或者,如果您使用的是 jQuery 1.5,您还可以使用新的 Deferred 对象做一些巧妙的事情。

【讨论】:

【解决方案3】:

多年来,我一直在尝试概括这种过程,但就我而言,我只关心链中第一个事件侦听器的顺序。

如果有任何用处,这里是我的 jQuery 插件,它绑定了一个事件监听器,它总是在任何其他事件之前触发:

** 已更新内联与 jQuery 更改(感谢 Toskan) **

(function($) {
    $.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
        var indexOfDot = eventType.indexOf(".");
        var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";

        eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
        handler = handler == undefined ? eventData : handler;
        eventData = typeof eventData == "function" ? {} : eventData;

        return this.each(function() {
            var $this = $(this);
            var currentAttrListener = this["on" + eventType];

            if (currentAttrListener) {
                $this.bind(eventType, function(e) {
                    return currentAttrListener(e.originalEvent); 
                });

                this["on" + eventType] = null;
            }

            $this.bind(eventType + eventNameSpace, eventData, handler);

            var allEvents = $this.data("events") || $._data($this[0], "events");
            var typeEvents = allEvents[eventType];
            var newEvent = typeEvents.pop();
            typeEvents.unshift(newEvent);
        });
    };
})(jQuery);

注意事项:

  • 这尚未经过全面测试。
  • 它依赖于不改变的 jQuery 框架的内部结构(仅在 1.5.2 中测试过)。
  • 它不一定会在以任何方式绑定的事件侦听器之前触发,而不是作为源元素的属性或使用 jQuery bind() 和其他相关函数。

【讨论】:

【解决方案4】:

绑定回调的调用顺序由每个 jQuery 对象的事件数据管理。没有任何函数(据我所知)允许您直接查看和操作该数据,您只能使用 bind() 和 unbind()(或任何等效的辅助函数)。

Dowski 的方法是最好的,您应该修改各种绑定回调以绑定到自定义事件的有序序列,而“第一个”回调绑定到“真实”事件。这样,无论它们以什么顺序绑定,序列都会以正确的方式执行。

我能看到的唯一替代方案是您真的非常不想考虑的事情:如果您知道函数的绑定语法可能在您之前已绑定,请尝试取消绑定所有这些函数,然后重新- 自己按正确的顺序绑定它们。那只是自找麻烦,因为现在你有重复的代码。

如果 jQuery 允许您简单地更改对象事件数据中绑定事件的顺序,而不需要编写一些代码来挂钩到 jQuery 核心,那将是很酷的,这似乎是不可能的。允许这样做可能会产生我没有想到的含义,所以这可能是故意的遗漏。

【讨论】:

  • 查看 jquery 绑定到元素的所有事件使用 var allEvents = $.data( this, "events" );
【解决方案5】:

请注意,在 jQuery 世界中,这必须与版本 1.8 不同地实现。以下发行说明来自the jQuery blog:

.data(“events”):jQuery 将其与事件相关的数据存储在数据对象中 每个元素上的命名(等待它)事件。这是内部数据 结构,因此在 1.8 中,这将从用户数据名称空间中删除 所以它不会与同名的项目冲突。 jQuery的事件数据 仍然可以通过 jQuery._data(element, "events") 访问

我们确实可以完全控制处理程序在 jQuery 世界中执行的顺序。 Ricoo 在上面指出了这一点。看起来他的回答并没有为他赢得很多爱,但这个技巧非常得心应手。例如,考虑任何时候您需要在库小部件中的某个处理程序之前执行自己的处理程序,或者您需要有条件地取消对小部件处理程序的调用:

$("button").click(function(e){
    if(bSomeConditional)
       e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
    var aClickListeners = $._data(this, "events").click;
    aClickListeners.reverse();
});

【讨论】:

    【解决方案6】:
    function bindFirst(owner, event, handler) {
        owner.unbind(event, handler);
        owner.bind(event, handler);
    
        var events = owner.data('events')[event];
        events.unshift(events.pop());
    
        owner.data('events')[event] = events;
    }
    

    【讨论】:

      【解决方案7】:

      只需正常绑定处理程序,然后运行:

      element.data('events').action.reverse();
      

      例如:

      $('#mydiv').data('events').click.reverse();
      

      【讨论】:

      • 不工作,但这个$._data(element, 'events')[name].reverse()工作
      【解决方案8】:

      你可以试试这样的:

      /**
        * Guarantee that a event handler allways be the last to execute
        * @param owner The jquery object with any others events handlers $(selector)
        * @param event The event descriptor like 'click'
        * @param handler The event handler to be executed allways at the end.
      **/
      function bindAtTheEnd(owner,event,handler){
          var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
          bindAtTheStart(owner,event,aux,true);
      
      }
      /**
        * Bind a event handler at the start of all others events handlers.
        * @param owner Jquery object with any others events handlers $(selector);
        * @param event The event descriptor for example 'click';
        * @param handler The event handler to bind at the start.
        * @param one If the function only be executed once.
      **/
      function bindAtTheStart(owner,event,handler,one){
          var eventos,index;
          var handlers=new Array();
          owner.unbind(event,handler);
          eventos=owner.data("events")[event];
          for(index=0;index<eventos.length;index+=1){
              handlers[index]=eventos[index];
          }
          owner.unbind(event);
          if(one){
              owner.one(event,handler);
          }
          else{
              owner.bind(event,handler);
          }
          for(index=0;index<handlers.length;index+=1){
              owner.bind(event,ownerhandlers[index]);
          }   
      }
      

      【讨论】:

      • 我认为“ownerhandlers”应该只是“handlers”
      【解决方案9】:

      我有同样的问题并找到了这个主题。以上答案可以解决这些问题,但我认为它们不是好的计划。

      让我们想想现实世界。

      如果我们使用这些答案,我们必须更改我们的代码。你必须改变你的代码风格。像这样:

      原文:

      $('form').submit(handle);
      

      破解:

      bindAtTheStart($('form'),'submit',handle);
      

      随着时间的推移,想想你的项目。代码丑陋且难以阅读!另一个原因很简单总是更好。如果你有 10 个 bindAtTheStart,它可能没有错误。如果你有 100 个 bindAtTheStart,你真的确定你可以让它们保持正确的顺序吗?

      所以如果你必须绑定多个相同的事件。我认为最好的方法是控制 js-file 或 js-code 加载顺序。 jquery 可以将事件数据作为队列处理。顺序是先进先出。您不需要更改任何代码。只需更改加载顺序即可。

      【讨论】:

        【解决方案10】:

        这是我对此的看法,涵盖了不同版本的 jQuery:

        // Binds a jQuery event to elements at the start of the event chain for that type.
        jQuery.extend({
            _bindEventHandlerAtStart: function ($elements, eventType, handler) {
                var _data;
        
                $elements.bind(eventType, handler);
                // This bound the event, naturally, at the end of the event chain. We
                // need it at the start.
        
                if (typeof jQuery._data === 'function') {
                    // Since jQuery 1.8.1, it seems, that the events object isn't
                    // available through the public API `.data` method.
                    // Using `$._data, where it exists, seems to work.
                    _data = true;
                }
        
                $elements.each(function (index, element) {
                    var events;
        
                    if (_data) {
                        events = jQuery._data(element, 'events')[eventType];
                    } else {
                        events = jQuery(element).data('events')[eventType];
                    }
        
                    events.unshift(events.pop());
        
                    if (_data) {
                        jQuery._data(element, 'events')[eventType] = events;
                    } else {
                        jQuery(element).data('events')[eventType] = events;
                    }
                });
            }
        });
        

        【讨论】:

          【解决方案11】:

          在某些特殊情况下,当您无法更改点击事件的绑定方式(事件绑定是由别人的代码进行的),而您可以更改 HTML 元素时,这里有一个可能的解决方案(警告:这不是推荐的绑定事件的方式,其他开发者可能会因此而谋杀你):

          <span onclick="yourEventHandler(event)">Button</span>
          

          通过这种绑定方式,你的事件处理器会先添加,所以会先执行。

          【讨论】:

          • 这不是解决方案,而是解决方案的提示。您是否建议这个“onclick”处理程序正在处理已经具有(jQuery)处理程序的元素,或者您的 wrapping 带有 jQ​​uery 处理程序的元素?
          【解决方案12】:

          JQuery 1.5 引入了 Promise,这是我见过的用于控制执行顺序的最简单的实现。完整文档在http://api.jquery.com/jquery.when/

          $.when( $('#myDiv').css('background-color', 'red') )
           .then( alert('hi!') )
           .then( myClickFunction( $('#myID') ) )
           .then( myThingToRunAfterClick() );
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-14
            • 1970-01-01
            • 1970-01-01
            • 2020-02-26
            相关资源
            最近更新 更多