jQuery事件处理程序总是按照绑定的顺序执行 - 任何方式都可以解决这个问题?

时间:2010-03-02 03:06:55

标签: jquery

jQuery事件处理程序总是以绑定的顺序执行,这可能很烦人。例如:

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

点击该范围将导致doStuff1()触发,然后doStuff2()

当我绑定doStuff2()时,我希望选择在 doStuff1()之前绑定它 ,但似乎没有任何简单的方法这样做。

我想大多数人会说,只需写下这样的代码:

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

但这只是一个简单的例子 - 在实践中,这样做并不总是方便。

在某些情况下,您希望绑定事件,并且您绑定的对象已经有事件。在这种情况下,您可能只希望在任何其他现有事件之前触发新事件。

那么在jQuery中实现这一目标的最佳方法是什么?

10 个答案:

答案 0 :(得分:116)

更新了答案

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

这是一个playground


原始答案

正如@Sean所发现的那样,jQuery通过元素的data接口公开所有事件处理程序。具体来说是element.data('events')。使用它你总是可以写一个简单的插件,你可以在特定的位置插入任何事件处理程序。

这是一个简单的插件,可以在列表的开头插入一个处理程序。您可以轻松扩展此项以在任何给定位置插入项目。这只是数组操作。但是由于我没有看到jQuery的源代码并且不想错过任何jQuery魔法,我通常首先使用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);
};

因此,例如,对于此标记,它将作为(example here):

<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的更新可能会破坏您的代码例如,附加事件从数组变为其他事件。

免责声明:由于一切皆有可能:),这是您的解决方案,但我仍然会在重构现有代码方面犯错,因为只是想记住这些项目附加的顺序很快就会消失随着你不断添加越来越多的这些有序事件。

答案 1 :(得分:33)

您可以执行事件的自定义命名空间。

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

另外,只需按照绑定的顺序触发click LOULD触发...所以你仍然可以

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

答案 2 :(得分:12)

一个非常好的问题......我很感兴趣所以我做了一点挖掘;对于那些感兴趣的人,这里是我去的地方,以及我想出的。

查看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分钟...所以我搜索了Stackoverflow

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

并发现this answer用于迭代绑定事件:

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

    });

});

在Firefox中测试我看到每个元素的events属性中的data对象都有一个[some_event_name]属性(在我们的例子中是click)被附加到一组handler个对象,每个对象都有一个guid,一个命名空间,一个类型和一个处理程序。 “那么”,我认为,“我们理论上应该能够以相同的方式将对象添加到[element].data.events.[some_event_name].push([our_handler_object); ......”

然后我去写完我的发现...并找到一个很多更好的答案由RusselUresti发布...它向我介绍了一些我不了解的关于jQuery的新东西(即使我正盯着它。)

这证明Stackoverflow是互联网上最好的问答网站,至少在我的拙见中。

所以我发布这个是为了后人的缘故......并将其标记为社区维基,因为RussellUresti已经很好地回答了这个问题。

答案 3 :(得分:4)

标准原则是单独的事件处理程序不应该依赖于它们被调用的顺序。如果他们确实依赖于订单,他们就不应该分开。

否则,您将一个事件处理程序注册为“first”,然后其他人将其事件处理程序注册为“first”,然后您又回到了以前的混乱状态。

答案 4 :(得分:4)

.data(“events”)已在版本1.9和2.0beta中删除,因此您无法再依赖这些解决方案了。

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

答案 5 :(得分:3)

对于jQuery 1.9+,Dunstkreis提及.data(&#39; events&#39;)已删除。 但你可以使用另一个hack(不建议使用未记录的可能性) $ ._ data($(this).get(0),&#39; events&#39;)而anurag提供的解决方案将如下所示:

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

答案 6 :(得分:3)

Anurag创作的选定答案仅部分正确。由于jQuery事件处理的一些内部结构,如果你有多个带有和不带过滤器的处理程序(即:$(document).on(“click”,handler)vs $(document).on,那么建议的bindFirst函数将不起作用.on (“点击”,“按钮”,处理程序))。

问题是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.first(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);
    });
};

答案 7 :(得分:2)

克里斯奇尔弗斯的建议应该是第一个行动方案,但有时我们正在处理第三方图书馆,这使得这个具有挑战性,并要求我们做顽皮的事情......这就是。 IMO这是一种推定犯罪,类似于在CSS中使用!important。

话虽如此,基于Anurag的回答,这里有一些补充。这些方法允许多个事件(例如“keydown keyup paste”),处理程序的任意定位以及事后的重新排序。

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

可以使用在给定处理程序之前或之后放置给定处理程序的方法来推断这一点。

答案 8 :(得分:1)

我假设你在谈论它的事件冒泡方面。查看所述span元素的HTML也会很有帮助。我不明白你为什么要改变这样的核心行为,我一点也不觉得烦人。我建议你使用第二块代码:

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

最重要的是,如果您管理同一块中给定元素的所有事件(如图所示),我认为您会发现它更有条理。你能解释为什么你觉得这很烦人吗?

答案 9 :(得分:1)

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