如何订购与jQuery绑定的事件


164

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

我将一些onclick事件绑定到按钮,但是我发现它们有时会按我不期望的顺序执行。

有没有办法确保订单,或者您过去如何处理此问题?


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

Answers:


28

我花了很长时间来概括这种过程,但就我而言,我只关心链中第一个事件侦听器的顺序。

如果有什么用,这里是我的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()和其他关联函数进行绑定。

重要的是要注意,这也仅适用于通过jQuery添加的事件。
格林

1
这不工作了,因为它使用了this.data("events"),看到这里stackoverflow.com/questions/12214654/...
Toskan

4
这里有针对jQuery 1.8的类似功能已更新:stackoverflow.com/a/2641047/850782
EpicVoyage 2014年

136

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

$('#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;
});

至少是这样的。


23
如果我们想停止在绑定代码之前的某个时刻定义的回调的click事件传播,该怎么办?
vinilios 2011年

2
我可以问为什么这是不被接受的吗?当前接受的答案将我的答案称为最佳方法,所以我有点困惑。:-)
dowski

这适用于mkoryak的情况,但如果多次调用同一事件,则无效。我有一个类似的问题,单个击键以相反的顺序多次触发$(window).scroll()。
kxsong 2014年

37

如果所有回调都将始终存在并且您对它们相互依赖感到满意,则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对象进行巧妙的处理。


6
.delegate已不再使用,您现在应该使用.on,但我不知道您如何使用on
eric.itzhak 2012年

该插件已经拿到固定的,因为它不工作了看这里:stackoverflow.com/questions/12214654/...
Toskan

@ eric.itzhak要使.on()的行为类似于旧的.delegate(),只需为其提供选择器即可。此处的详细信息api.jquery.com/delegate
Sam Kauffman

如果您想利用冒泡的优势,我认为该事件必须是直接的而不是委托的api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

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

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

我可以看到的唯一选择是您确实非常不想考虑的事情:如果您知道这些函数的绑定语法可能已经在您之前绑定了,请尝试取消所有这些函数的绑定,然后重新进行绑定自己以正确的顺序。那只是自找麻烦,因为现在您已经重复了代码。

如果jQuery允许您简单地更改对象事件数据中绑定事件的顺序,但又无需编写一些似乎无法挂接到jQuery核心的代码,那将很酷。可能存在允许我没有想到的含义,因此这可能是故意遗漏的。


12
要查看jquery绑定到元素的所有事件,请使用var allEvents = $ .data(this,“ events”);
redsquare

9

请注意,在jQuery Universe中,此版本必须从1.8版开始以不同的方式实现。以下发行说明来自jQuery博客:

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

我们确实完全控制了处理程序在jQuery Universe中执行的顺序。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();
});

7

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

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

因此,例如:

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

2
无效,但是$._data(element, 'events')[name].reverse()有效
Attenzione 2015年

7
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;
}

3

您可以尝试如下操作:

/**
  * 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]);
    }   
}

我认为“所有者处理程序”应该只是“处理程序”
Joril 2011年

1

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

让我们考虑一下现实世界。

如果使用这些答案,则必须更改代码。您必须更改代码样式。像这样的东西:

原版的:

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

骇客:

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

随着时间的流逝,考虑一下您的项目。代码丑陋且难以阅读!原因简单总是更好。如果您有10 bindAtTheStart,则可能没有错误。如果您有100 bindAtTheStart,您真的确定可以使其顺序正确吗?

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


0

这是我的镜头,涵盖了不同版本的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;
            }
        });
    }
});

0

在某些特殊情况下,当您无法更改单击事件的绑定方式(事件绑定是由其他人的代码完成),并且可以更改HTML元素时,这是一种可能的解决方案(警告:这不是建议的绑定方式事件,其他开发者可能为此谋杀您):

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

通过这种绑定方式,将首先添加事件处理程序,因此将首先执行它。


那不是解决方案,而是对解决方案的暗示。您是在建议该“ onclick”处理程序在已经具有(jQuery)处理程序的元素上运行,还是您的<span> 用jQuery处理程序包装该元素?
Auspex

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.