javascript - Hur man ordnar händelser bundna med jQuery


Låt oss säga att jag har en webbapp som har en sida som kan innehålla 4 skriptblock - skriptet jag skriver kan finnas i ett av dessa block, men jag vet inte vilket, det hanteras av kontrollern.

Jag binder några onclick-händelser till en knapp, men jag märker att de ibland körs i en ordning jag inte förväntade mig.

Finns det ett sätt att säkerställa ordning, eller hur har du hanterat detta problem tidigare?

Svar #1

Om ordningen är viktig kan du skapa dina egna händelser och binda återuppringningar för att aktiveras när dessa händelser utlöses av andra återuppringningar.

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

Något sådant åtminstone.

Svar #2

Dowskis metod är bra om alla dina callback-funktioner alltid kommer att finnas och du är nöjd med att de är beroende av varandra.

Om du vill att återanropen ska vara oberoende av varandra, kan du däremot dra nytta av bubblingen och fästa efterföljande händelser som delegater till föräldrarnas element. Handlarna på ett förälderelement kommer att utlösas efter handlarna på elementet, och fortsätta upp till dokumentet. Detta är ganska bra eftersom du kan använda event.stopPropagation(), event.preventDefault() osv. för att hoppa över handlare och avbryta eller inte avbryta åtgärden.

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

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

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

Eller, om du inte gillar detta, kan du använda Nick Leaches bindLast-plugin för att tvinga en händelse att bindas sist: https://github.com/nickyleach/jQuery.bindLast.

Eller, om du använder jQuery 1.5, kan du också potentiellt göra något smart med det nya Deferred-objektet.

Svar #3

Jag hade försökt i evigheter att generalisera den här typen av process, men i mitt fall var jag bara intresserad av ordningen på den första händelselyssnaren i kedjan.

Om det är till någon nytta, här är min jQuery-plugin som binder en händelselyssnare som alltid triggas före alla andra:

** UPPDATERAD i enlighet med jQuery-ändringar (tack 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);

Saker att notera:

  • Detta har inte testats fullt ut.
  • Det beror på att jQuery-ramverkets interna delar inte förändras (testad endast med 1.5.2).
  • Det kommer inte nödvändigtvis att utlösas före händelselyssnare som är bundna på annat sätt än som en attribut på källelementet eller med hjälp av jQuery bind() och andra associerade funktioner.

Svar #4

Ordningen som de bundna callback-funktionerna anropas i hanteras av varje jQuery-objekts händelsedata. Det finns inga funktioner (såvitt jag vet) som låter dig se och manipulera dessa data direkt, du kan bara använda bind() och unbind() (eller någon av de motsvarande hjälpfunktionerna).

Dowskis metod är bäst, du bör modifiera de olika bundna callbackerna för att binda till en ordnad sekvens av anpassade händelser, med den "första" callbacken bunden till den "verkliga" händelsen. På så sätt, oavsett i vilken ordning de är bundna, kommer sekvensen att köras på rätt sätt.

Det enda alternativ jag kan se är något du verkligen, verkligen inte vill överväga: om du vet att bindningssyntaxen för funktionerna kan ha bundits före dig, försök att avbinda alla dessa funktioner och sedan binda dem igen i rätt ordning själv. Det är bara att be om problem, eftersom du nu har duplicerad kod.

Det vore häftigt om jQuery tillät dig att helt enkelt ändra ordningen på de bundna händelserna i ett objekts händelsedata, men utan att skriva någon kod för att koppla in sig i jQuery-kärnan verkar det inte möjligt. Och det finns förmodligen konsekvenser av att tillåta detta som jag inte har tänkt på, så kanske är det ett medvetet uteslutande.

Svar #5

Observera att i jQuery-universumet måste detta implementeras på ett annat sätt från och med version 1.8. Följande versionsnotering är från jQuery-bloggen:

.data("events"): jQuery lagrar sin händelserelaterade data i ett dataobjekt som heter (håll i dig) events på varje element. Detta är en intern datastruktur, så i version 1.8 kommer detta att tas bort från användardatasäkerhetsnamnrymden så att det inte kolliderar med objekt med samma namn. jQuery:s händelsedata kan fortfarande nås via jQuery._data(element, "events")

Vi har fullständig kontroll över ordningen i vilken handlare kommer att köras i jQuery-universumet. Ricoo påpekar detta ovan. Det verkar inte som att hans svar fick honom mycket uppskattning, men denna teknik är mycket praktisk. Tänk till exempel på när du behöver köra din egen handlare före någon handlare i en bibliotekskomponent, eller när du behöver ha möjligheten att villkorligt avbryta anropet till komponentens handlare:

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

Relaterade artiklar