code

jQuery 이벤트 처리기는 항상 바인딩된 순서대로 실행합니다. 이 문제를 해결할 방법이 있습니까?

starcafe 2023. 8. 11. 22:28
반응형

jQuery 이벤트 처리기는 항상 바인딩된 순서대로 실행합니다. 이 문제를 해결할 방법이 있습니까?

jQuery 이벤트 처리기가 바인딩된 순서대로 항상 실행하는 것은 귀찮은 일이 될 수 있습니다.예:

$('span').click(doStuff1);
$('span').click(doStuff2);

하면 "" "" "" ""가 합니다.doStuff1()발포한 후에.doStuff2().

doStuff2()를 바인딩할 때 doStuff1()보다 먼저 바인딩하는 옵션을 원하지만 이를 위한 쉬운 방법은 없는 것 같습니다.

대부분의 사람들은 이렇게 말할 것입니다. 코드를 이렇게 쓰십시오.

$('span').click(function (){
    doStuff2();
    doStuff1();
});

하지만 이것은 단순한 예에 불과합니다. 실제로 그렇게 하는 것이 항상 편리한 것은 아닙니다.

이벤트를 바인딩하려는 경우 바인딩할 개체에 이미 이벤트가 있는 경우가 있습니다.이 경우 기존의 다른 이벤트보다 먼저 새 이벤트가 시작되기를 원할 수 있습니다.

그렇다면 jQuery에서 이를 달성하는 가장 좋은 방법은 무엇일까요?

업데이트된 답변

jQuery는 1.8에서 이벤트가 저장되는 위치를 변경했습니다.이제 내부 API를 만지작거리는 것이 왜 그렇게 나쁜 생각인지 알게 되었습니다 :)

DOM 개체의 이벤트에 액세스하기 위한 새로운 내부 API는 각 인스턴스에 연결되지 않고 글로벌 jQuery 개체를 통해 사용할 수 있으며, 첫 번째 매개 변수로 DOM 요소를, 두 번째 매개 변수로 키("이벤트")를 사용합니다.

jQuery._data(<DOM element>, "events");

여기 jQuery 1.8의 수정된 코드가 있습니다.

// [name] is the name of the event "click", "mouseover", .. 
// same as you'd pass it to bind()
// [fn] is the handler function
$.fn.bindFirst = function(name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.on(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    this.each(function() {
        var handlers = $._data(this, 'events')[name.split('.')[0]];
        // take out the handler we just inserted from the end
        var handler = handlers.pop();
        // move it at the beginning
        handlers.splice(0, 0, handler);
    });
};

여기 놀이터가 있습니다.


원답

@@Sean이는것처럼, ▁an'▁event▁handlers▁as▁through▁all▁element▁j다▁j니합출노seququ▁disc해▁@통,▁@overedse네를트▁exposes크s▁has워소의요발을 통해 모든 이벤트 핸들러를 노출시킵니다.data인터페이스구체적으로element.data('events')이를 사용하면 항상 특정 위치에 이벤트 핸들러를 삽입할 수 있는 간단한 플러그인을 작성할 수 있습니다.

목록의 시작 부분에 핸들러를 삽입하는 간단한 플러그인이 있습니다.쉽게 확장하여 지정된 위치에 항목을 삽입할 수 있습니다.배열 조작일 뿐입니다. 저는 본 적이 않기 에, 저는 하만나는지 ▁the▁but▁add▁fromqu▁iqu,하를 사용하여 핸들러를 합니다.bind먼저 배열을 변경합니다.

// [name] is the name of the event "click", "mouseover", .. 
// same as you'd pass it to bind()
// [fn] is the handler function
$.fn.bindFirst = function(name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.bind(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    var handlers = this.data('events')[name.split('.')[0]];
    // take out the handler we just inserted from the end
    var handler = handlers.pop();
    // move it at the beginning
    handlers.splice(0, 0, handler);
};

예를 들어, 이 마크업의 경우 다음과 같이 작동합니다(여기 예).

<div id="me">..</div>

$("#me").click(function() { alert("1"); });
$("#me").click(function() { alert("2"); });    
$("#me").bindFirst('click', function() { alert("3"); });

$("#me").click(); // alerts - 3, then 1, then 2

하지만, 그 이후로.data('events')제가 알기로는 공개 API의 일부가 아닙니다. 예를 들어 첨부된 이벤트의 기본 표현이 배열에서 다른 것으로 변경되는 경우 jQuery로 업데이트하면 코드가 손상될 수 있습니다.

고지 사항:모든 것이 가능하기 때문에:), 해결책은 여기에 있지만, 저는 여전히 당신의 기존 코드를 리팩터링하는 쪽에서 실수를 할 것입니다. 왜냐하면 당신이 이러한 주문된 이벤트를 계속해서 추가함에 따라 이 항목들이 부착된 순서를 기억하려고 노력하는 것이 곧 통제할 수 없기 때문입니다.

이벤트의 사용자 지정 네임스페이스를 수행할 수 있습니다.

$('span').bind('click.doStuff1',function(){doStuff1();});
$('span').bind('click.doStuff2',function(){doStuff2();});

그런 다음 트리거해야 할 경우 순서를 선택할 수 있습니다.

$('span').trigger('click.doStuff1').trigger('click.doStuff2');

또는

$('span').trigger('click.doStuff2').trigger('click.doStuff1');

또한, 클릭을 트리거하기만 하면 바인딩된 순서대로 둘 다 트리거해야 합니다.그래서 당신은 여전히 할 수 있습니다.

$('span').trigger('click'); 

아주 좋은 질문입니다...저는 호기심이 생겨서 약간의 발굴을 했습니다. 관심있는 사람들을 위해, 여기가 제가 어디로 갔는지, 그리고 제가 생각해낸 것입니다.

jQuery 1.4.2의 소스 코드를 보니 2361과 2392 사이에 이 블록이 있었습니다.

jQuery.each(["bind", "one"], function( i, name ) {
    jQuery.fn[ name ] = function( type, data, fn ) {
        // Handle object literals
        if ( typeof type === "object" ) {
            for ( var key in type ) {
                this[ name ](key, data, type[key], fn);
            }
            return this;
        }

        if ( jQuery.isFunction( data ) ) {
            fn = data;
            data = undefined;
        }

        var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
            jQuery( this ).unbind( event, handler );
            return fn.apply( this, arguments );
        }) : fn;

        if ( type === "unload" && name !== "one" ) {
            this.one( type, data, fn );

        } else {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                jQuery.event.add( this[i], type, handler, data );
            }
        }

        return this;
    };
});

여기서 많은 흥미로운 일들이 벌어지고 있지만, 우리가 관심을 가지고 있는 부분은 2384와 2388 사이에 있습니다.

else {
    for ( var i = 0, l = this.length; i < l; i++ ) {
        jQuery.event.add( this[i], type, handler, data );
    }
}

가 전화때다를 부를 마다.bind()또는one()우리는 실제로 전화를 하고 있습니다.jQuery.event.add()그래서 그것을 살펴봅시다 (만약 당신이 관심이 있다면, 1557에서 1672행).

add: function( elem, types, handler, data ) {
// ... snip ...
        var handleObjIn, handleObj;

        if ( handler.handler ) {
            handleObjIn = handler;
            handler = handleObjIn.handler;
        }

// ... snip ...

        // Init the element's event structure
        var elemData = jQuery.data( elem );

// ... snip ...

        var events = elemData.events = elemData.events || {},
            eventHandle = elemData.handle, eventHandle;

        if ( !eventHandle ) {
            elemData.handle = eventHandle = function() {
                // Handle the second event of a trigger and when
                // an event is called after a page has unloaded
                return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
                    jQuery.event.handle.apply( eventHandle.elem, arguments ) :
                    undefined;
            };
        }

// ... snip ...

        // Handle multiple events separated by a space
        // jQuery(...).bind("mouseover mouseout", fn);
        types = types.split(" ");

        var type, i = 0, namespaces;

        while ( (type = types[ i++ ]) ) {
            handleObj = handleObjIn ?
                jQuery.extend({}, handleObjIn) :
                { handler: handler, data: data };

            // Namespaced event handlers
                    ^
                    |
      // There is is! Even marked with a nice handy comment so you couldn't miss it 
      // (Unless of course you are not looking for it ... as I wasn't)

            if ( type.indexOf(".") > -1 ) {
                namespaces = type.split(".");
                type = namespaces.shift();
                handleObj.namespace = namespaces.slice(0).sort().join(".");

            } else {
                namespaces = [];
                handleObj.namespace = "";
            }

            handleObj.type = type;
            handleObj.guid = handler.guid;

            // Get the current list of functions bound to this event
            var handlers = events[ type ],
                special = jQuery.event.special[ type ] || {};

            // Init the event handler queue
            if ( !handlers ) {
                handlers = events[ type ] = [];

                   // ... snip ...

            }

                  // ... snip ...

            // Add the function to the element's handler list
            handlers.push( handleObj );

            // Keep track of which events have been used, for global triggering
            jQuery.event.global[ type ] = true;
        }

     // ... snip ...
    }

이 시점에서 이를 이해하는 데 30분 이상이 걸릴 것이라는 것을 깨달았습니다... 그래서 스택 오버플로를 검색했습니다.

jquery get a list of all event handlers bound to an element

오버바운드 이벤트를 반복하기 위한 을 찾았습니다.

//log them to the console (firebug, ie8)
console.dir( $('#someElementId').data('events') );

//or iterate them
jQuery.each($('#someElementId').data('events'), function(i, event){

    jQuery.each(event, function(i, handler){

        console.log( handler.toString() );

    });

});

파이어폭스에서 테스트해보니events에 있는 data모든 요소의 속성은 다음을 가집니다.[some_event_name]속성)click우리의 경우)의 배열이 첨부되어 있습니다.handler각 개체에는 GUID, 네임스페이스, 유형 및 처리기가 있습니다.제 생각에 "그래서", "이론적으로 동일한 방식으로 구축된 객체를 추가할 수 있어야 합니다.[element].data.events.[some_event_name].push([our_handler_object);... "

그리고 저는 제 연구 결과를 작성하는 것을 마칩니다... 그리고 Russel Uresti가 올린 훨씬 더 나은 답을 찾습니다...제가 jQuery에 대해 몰랐던 새로운 것을 소개합니다(제가 직접 보고 있었음에도 불구하고).

이것은 스택 오버플로가 인터넷에서 최고의 질의응답 사이트라는 증거입니다. 적어도 제 겸손한 의견으로는요.

그래서 저는 후세를 위해 이 글을 올립니다. 그리고 커뮤니티 위키로 표시합니다. 러셀 유레스티가 이미 질문에 아주 잘 대답했기 때문입니다.

표준 원칙은 개별 이벤트 처리기가 호출되는 순서에 따라 달라지지 않아야 한다는 것입니다.만약 그들이 순서에 따라 다르다면, 그들은 분리되어서는 안 됩니다.

그렇지 않으면 한 이벤트 핸들러를 '첫 번째'로 등록하고 다른 사람이 이벤트 핸들러를 '첫 번째'로 등록하면 이전과 같은 혼란 상태로 돌아갑니다.

Anurag가 작성한 선택한 답변은 부분적으로만 정답입니다.jQuery의 이벤트 처리의 일부 내부로 인해 필터가 있는 핸들러와 없는 핸들러(예: $(document) vs $(document).on("클릭", "버튼", 핸들러)이 있는 핸들러가 있는 경우 제안된 bindFirst 함수가 작동하지 않습니다.

문제는 jQuery가 처리기 배열의 첫 번째 요소를 이러한 필터링된 처리기로 배치(그리고 예상)하기 때문에 처음에 필터 없이 이벤트를 배치하면 이 논리가 깨지고 모든 것이 붕괴되기 시작한다는 것입니다.업데이트된 bindFirst 함수는 다음과 같아야 합니다.

$.fn.bindFirst = function (name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.on(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    this.each(function () {
        var handlers = $._data(this, 'events')[name.split('.')[0]];
        // take out the handler we just inserted from the end
        var handler = handlers.pop();
        // get the index of the first handler without a selector
        var firstNonDelegate = handlers.find(function(h) { return !h.selector; });
        var index = firstNonDelegate ? handlers.indexOf(firstNonDelegate)
                                     : handlers.length; // Either all handlers are selectors or we have no handlers
        // move it at the beginning
        handlers.splice(index, 0, handler);
    });
};

.datastore")는 버전 1.9 및 2.0에서 제거되었으므로 더 이상 이러한 솔루션에 의존할 수 없습니다.

http://jquery.com/upgrade-guide/1.9/ #data-messages-messages-messages-

dunstkreis로 언급된 jQuery 1.9+의 경우 .data('events')가 제거되었습니다.그러나 다른 해킹을 사용할 수 있습니다(문서화되지 않은 가능성은 사용하지 않는 것이 좋습니다) $._data($(이것)).대신 get(0), 'discovery'), 그리고 urag가 제공하는 솔루션은 다음과 같습니다.

$.fn.bindFirst = function(name, fn) {
    this.bind(name, fn);
    var handlers = $._data($(this).get(0), 'events')[name.split('.')[0]];
    var handler = handlers.pop();
    handlers.splice(0, 0, handler);
};

Chris Chilvers의 조언이 첫 번째 행동 방침이어야 하지만 때때로 우리는 이것을 도전적으로 만들고 우리에게 장난스러운 것들을 요구하는 서드파티 도서관을 다루고 있습니다.여기가 바로.IMO CSS에서 !important를 사용하는 것과 유사한 추정 범죄입니다.

그렇긴 하지만, 아누락의 대답을 바탕으로, 여기 몇 가지 추가 사항이 있습니다.이러한 방법을 사용하면 여러 이벤트(예: "키다운 키업 페이스트"), 처리기의 임의 위치 지정 및 사실 이후 재정렬이 가능합니다.

$.fn.bindFirst = function (name, fn) {
    this.bindNth(name, fn, 0);
}

$.fn.bindNth(name, fn, index) {
    // Bind event normally.
    this.bind(name, fn);
    // Move to nth position.
    this.changeEventOrder(name, index);
};

$.fn.changeEventOrder = function (names, newIndex) {
    var that = this;
    // Allow for multiple events.
    $.each(names.split(' '), function (idx, name) {
        that.each(function () {
            var handlers = $._data(this, 'events')[name.split('.')[0]];
            // Validate requested position.
            newIndex = Math.min(newIndex, handlers.length - 1);
            handlers.splice(newIndex, 0, handlers.pop());
        });
    });
};

특정 핸들러를 다른 핸들러 앞이나 뒤에 배치하는 방법으로 이를 추정할 수 있습니다.

저는 당신이 그 사건의 거품이 일고 있는 측면에 대해 이야기하고 있다고 생각합니다.당신의 HTML을 보는 것이 도움이 될 것입니다.span요소들 또한.저는 당신이 왜 이런 핵심적인 행동을 바꾸려고 하는지 이해할 수 없습니다. 저는 그것이 전혀 귀찮다고 생각하지 않습니다.두 번째 코드 블록을 사용하는 것이 좋습니다.

$('span').click(function (){
  doStuff2();
  doStuff1();
});

가장 중요한 것은 그림에서 설명한 것처럼 특정 요소에 대한 모든 이벤트를 동일한 블록에서 관리하면 더 체계적이라는 것을 알게 될 것입니다.당신이 이것을 왜 짜증나게 하는지 설명할 수 있습니까?

다음은 jQuery 1.4.x에 대한 솔루션입니다(안타깝게도 jQuery 1.4.1에 대해 수락된 답변이 작동하지 않았습니다).

$.fn.bindFirst = function(name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.bind(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    var handlers = this.data('events')[name.split('.')[0]];
    // take out the handler we just inserted from the end
    var copy = {1: null};

    var last = 0, lastValue = null;
    $.each(handlers, function(name, value) {
        //console.log(name + ": " + value);
        var isNumber = !isNaN(name);
        if(isNumber) {last = name; lastValue = value;};

        var key = isNumber ? (parseInt(name) + 1) : name;
        copy[key] = value;
    });
    copy[1] = lastValue;
    this.data('events')[name.split('.')[0]] = copy;
};

언급URL : https://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t

반응형