วิธีการสั่งซื้อเหตุการณ์ที่ถูกผูกไว้กับ jQuery


164

ให้บอกว่าฉันมีเว็บแอพที่มีหน้าเว็บที่อาจมี 4 บล็อกสคริปต์ - สคริปต์ที่ฉันเขียนอาจพบได้ในหนึ่งในบล็อกเหล่านั้น แต่ฉันไม่ทราบว่าตัวควบคุมนั้นถูกจัดการโดยตัวควบคุมใด

ฉันผูกonclickเหตุการณ์บางอย่างกับปุ่ม แต่ฉันพบว่าบางครั้งพวกเขาดำเนินการตามคำสั่งที่ฉันไม่ได้คาดหวัง

มีวิธีที่จะทำให้มั่นใจในการสั่งซื้อหรือคุณจัดการกับปัญหานี้ได้อย่างไรในอดีต?


1
เพิ่ม callback ของคุณไปยังวัตถุ CallBack และเมื่อคุณต้องการเริ่มการทำงานคุณสามารถยิงพวกเขาทั้งหมดในครั้งเดียวและพวกเขาจะทำงานตามลำดับที่เพิ่มเข้ามา api.jquery.com/jQuery.Callbacks
Tyddlywink

คำตอบ:


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

สิ่งที่ควรทราบ:

  • สิ่งนี้ยังไม่ได้รับการทดสอบอย่างสมบูรณ์
  • มันขึ้นอยู่กับ internals ของกรอบ jQuery ที่ไม่เปลี่ยนแปลง (ทดสอบโดยใช้ 1.5.2 เท่านั้น)
  • มันไม่จำเป็นต้องถูกกระตุ้นก่อนที่ผู้ฟังเหตุการณ์ที่ถูกผูกไว้ในลักษณะอื่นใดนอกเหนือจากเป็นแอตทริบิวต์ขององค์ประกอบต้นฉบับหรือใช้ jQuery bind () และฟังก์ชั่นอื่น ๆ ที่เกี่ยวข้อง

เป็นสิ่งสำคัญที่จะต้องทราบว่าสิ่งนี้ใช้ได้กับกิจกรรมที่เพิ่มผ่าน jQuery เท่านั้น
Grinn

1
สิ่งนี้ไม่ทำงานอีกต่อไปตั้งแต่ใช้this.data("events")ดูที่นี่stackoverflow.com/questions/12214654/
......

4
มีฟังก์ชั่นที่คล้ายกันซึ่งได้รับการอัพเดตสำหรับ jQuery 1.8 ที่นี่: stackoverflow.com/a/2641047/850782
EpicVoyage

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
จะเกิดอะไรขึ้นถ้าเราต้องการหยุดการเผยแพร่เหตุการณ์การคลิกสำหรับการโทรกลับที่กำหนดไว้ในบางจุดก่อนรหัสผูกของเรา
vinilios

2
ฉันจะถามได้ไหมว่าทำไมสิ่งนี้ถึงไม่ได้รับการยอมรับ คำตอบที่ได้รับการยอมรับในปัจจุบันอ้างถึงคำตอบของฉันว่าเป็นวิธีที่ดีที่สุดดังนั้นฉันจึงสับสน :-)
dowski

สิ่งนี้ใช้ได้กับกรณีของ mkoryak แต่ไม่ใช่ถ้าเหตุการณ์เดียวกันนั้นถูกเรียกหลายครั้ง ฉันมีปัญหาที่คล้ายกันโดยที่การกดแป้นเดียวทำให้เกิด $ (หน้าต่าง) .scroll () หลายครั้งในลำดับย้อนกลับ
kxsong

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

หรือถ้าคุณทำไม่ได้เช่นนี้คุณสามารถใช้นิค leaches bindLast ปลั๊กอินที่จะบังคับให้เหตุการณ์ที่จะผูกพันล่าสุด: https://github.com/nickyleach/jQuery.bindLast

หรือถ้าคุณใช้ jQuery 1.5 คุณสามารถทำสิ่งที่ฉลาดด้วยวัตถุ Deferred ใหม่


6
.delegateไม่ได้ใช้อีกแล้วและตอนนี้คุณควรใช้.onแต่ฉันไม่รู้ว่าคุณจะสามารถทำพฤติกรรมแบบเดียวกันได้อย่างไรon
eric.itzhak

ปลั๊กอินจะต้องได้รับการแก้ไขเนื่องจากมันไม่ทำงานอีกต่อไปดูที่นี่: stackoverflow.com/questions/12214654/
......

@ eric.itzhak ในการทำให้. on () ทำตัวเหมือน. delegate () เก่าเพียงแค่ให้มันมีตัวเลือก รายละเอียดได้ที่นี่api.jquery.com/delegate
Sam Kauffman

หากคุณต้องการที่จะใช้ประโยชน์จากการเดือดปุด ๆ ผมคิดว่าความต้องการที่จะเป็นเหตุการณ์โดยตรงมากกว่าที่ได้รับมอบหมาย api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

ลำดับการเรียกกลับที่ถูกผูกไว้ถูกจัดการโดยข้อมูลเหตุการณ์ของวัตถุ jQuery แต่ละรายการ ไม่มีฟังก์ชั่นใด ๆ (ที่ฉันรู้) ที่อนุญาตให้คุณดูและจัดการข้อมูลนั้นโดยตรงคุณสามารถใช้การผูก () และ unbind () (หรือฟังก์ชั่นผู้ช่วยเทียบเท่า) ใด ๆ

วิธีการของ Dowski นั้นดีที่สุดคุณควรปรับเปลี่ยนการโทรกลับที่ถูกผูกไว้ต่าง ๆ เพื่อผูกกับลำดับเหตุการณ์ที่กำหนดเองโดยการโทรกลับ "ครั้งแรก" ที่ผูกไว้กับเหตุการณ์ "ของจริง" ด้วยวิธีนี้ไม่ว่าจะเรียงตามลำดับอะไรก็ตามลำดับจะดำเนินการในทิศทางที่ถูกต้อง

ทางเลือกเดียวที่ฉันเห็นคือสิ่งที่คุณจริง ๆ แล้วไม่ต้องการพิจารณา: ถ้าคุณรู้ว่าไวยากรณ์ผูกพันของฟังก์ชันอาจถูกผูกไว้ข้างหน้าคุณพยายามที่จะยกเลิกการผูกฟังก์ชั่นเหล่านั้นทั้งหมดแล้วผูกใหม่อีกครั้ง ในลำดับที่เหมาะสมด้วยตัวคุณเอง นั่นเป็นเพียงการขอปัญหาเพราะตอนนี้คุณมีรหัสที่ซ้ำกัน

มันจะเจ๋งถ้า jQuery อนุญาตให้คุณเปลี่ยนลำดับของเหตุการณ์ที่ถูกผูกไว้ในข้อมูลเหตุการณ์ของวัตถุ แต่ไม่มีการเขียนโค้ดบางอย่างเพื่อขอเข้าไปในคอร์ jQuery ที่ดูเหมือนจะเป็นไปไม่ได้ และอาจมีความหมายของการอนุญาตให้สิ่งนี้ที่ฉันไม่ได้คิดดังนั้นอาจเป็นการละเว้นโดยเจตนา


12
เพื่อดูเหตุการณ์ทั้งหมดที่ถูกผูกไว้กับองค์ประกอบโดย jquery ใช้ var allEvents = $ .data (นี่คือ "events");
redsquare

9

โปรดทราบว่าใน jQuery จักรวาลนี้จะต้องมีการใช้งานที่แตกต่างจากรุ่น 1.8 บันทึกย่อประจำรุ่นต่อไปนี้มาจากบล็อก jQuery:

.data (“ events”): jQuery เก็บข้อมูลที่เกี่ยวข้องกับเหตุการณ์ไว้ในวัตถุข้อมูลที่ชื่อ (รอให้มัน) เหตุการณ์ในแต่ละองค์ประกอบ นี่คือโครงสร้างข้อมูลภายในดังนั้นใน 1.8 นี้จะถูกลบออกจากพื้นที่ชื่อข้อมูลผู้ใช้ดังนั้นมันจะไม่ขัดแย้งกับรายการที่มีชื่อเดียวกัน ข้อมูลกิจกรรมของ jQuery ยังคงสามารถเข้าถึงได้ผ่านทาง jQuery._data (องค์ประกอบ, "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();
});

7

เพียงผูกตัวจัดการตามปกติแล้วเรียกใช้:

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

ตัวอย่างเช่น:

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

2
ไม่ทำงาน แต่ใช้$._data(element, 'events')[name].reverse()งานได้
Attenzione


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

ฉันคิดว่า "ownerhandlers" ควรเป็นเพียงแค่ "ตัวจัดการ"
Joril

1

ฉันมีปัญหาเดียวกันและพบหัวข้อนี้ คำตอบข้างต้นสามารถแก้ปัญหาเหล่านั้นได้ แต่ฉันไม่คิดว่ามันเป็นแผนการที่ดี

ให้เราคิดเกี่ยวกับโลกแห่งความจริง

ถ้าเราใช้คำตอบเหล่านั้นเราต้องเปลี่ยนรหัสของเรา คุณต้องเปลี่ยนรูปแบบรหัสของคุณ บางสิ่งเช่นนี้

เดิม:

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

สับ:

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

เมื่อเวลาผ่านไปคิดเกี่ยวกับโครงการของคุณ รหัสน่าเกลียดและอ่านยาก! เหตุผล anthoer ง่าย ๆ ดีกว่าเสมอ หากคุณมี 10 bindAtTheStart อาจไม่มีข้อผิดพลาด หากคุณมี 100 bindAtTheStart คุณแน่ใจหรือไม่ว่าคุณสามารถเก็บไว้ในลำดับที่ถูกต้อง?

ดังนั้นหากคุณต้องผูกเหตุการณ์เดียวกันหลายตัวฉันคิดว่าวิธีที่ดีที่สุดคือการควบคุมลำดับการโหลด js-file หรือ js-code 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

-2

JQuery 1.5 แนะนำสัญญาและนี่คือการติดตั้งที่ง่ายที่สุดที่ฉันเคยเห็นเพื่อควบคุมลำดับการดำเนินการ เอกสารฉบับเต็มได้ที่http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.