jQuery事件处理程序总是按照它们被绑定的顺序执行——有什么方法吗?(复制)

时间:2023-02-05 00:05:03

This question already has an answer here:

这个问题已经有了答案:

It can be anoying that jQuery event handlers always execute in the order they were bound. For example:

可以认为jQuery事件处理程序总是按照它们所绑定的顺序执行。例如:

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

clicking on the span will cause doStuff1() to fire, followed by doStuff2().

单击span将导致doStuff1()着火,然后是doStuff2()。

At the time I bind doStuff2(), I would like the option to bind it before doStuff1(), but there doesn't appear to be any easy way to do this.

当我绑定doStuff2()时,我希望在doStuff1()之前绑定它,但是似乎没有任何简单的方法可以做到这一点。

I suppose most people would say, just write the code like this:

我想大多数人会说,就像这样写代码:

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

But this is just a simple example - in practise it is not always convienient to do that.

但这只是一个简单的例子——在实践中并不总是适合这样做。

There are situations when you want to bind an event, and the object you are binding to already has events. And in this case you may simply want the new event to fire before any other existing events.

在某些情况下,您想要绑定一个事件,而要绑定到的对象已经有了事件。在这种情况下,您可能只是希望在任何其他现有事件之前触发新事件。

So what is the best way to achieve this in jQuery?

那么,用jQuery实现这一点的最佳方式是什么呢?

10 个解决方案

#1


110  

Updated Answer

更新后的答案

jQuery changed the location of where events are stored in 1.8. Now you know why it is such a bad idea to mess around with internal APIs :)

jQuery更改了事件存储在1.8中的位置。现在您知道了为什么使用内部api是如此糟糕的想法了:)

The new internal API to access to events for a DOM object is available through the global jQuery object, and not tied to each instance, and it takes a DOM element as the first parameter, and a key ("events" for us) as the second parameter.

新的内部API可以通过全局jQuery对象访问DOM对象的事件,而不是绑定到每个实例,它将DOM元素作为第一个参数,键(对于我们来说是“events”)作为第二个参数。

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

So here's the modified code for jQuery 1.8.

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

And here's a playground.

这里有一个操场。


Original Answer

原来的答案

As @Sean has discovered, jQuery exposes all event handlers through an element's data interface. Specifically element.data('events'). Using this you could always write a simple plugin whereby you could insert any event handler at a specific position.

正如@Sean所发现的,jQuery通过元素的数据接口公开了所有事件处理程序。特别是element.data(“事件”)。使用它,您可以编写一个简单的插件,您可以在特定位置插入任何事件处理程序。

Here's a simple plugin that does just that to insert a handler at the beginning of the list. You can easily extend this to insert an item at any given position. It's just array manipulation. But since I haven't seen jQuery's source and don't want to miss out on any jQuery magic from happening, I normally add the handler using bind first, and then reshuffle the array.

下面是一个简单的插件,它可以在列表的开头插入一个处理程序。您可以很容易地将其扩展到在任何给定位置插入一个项目。它只是数组操作。但是由于我还没有看到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);
};

So for example, for this markup it would work as (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

However, since .data('events') is not part of their public API as far as I know, an update to jQuery could break your code if the underlying representation of attached events changes from an array to something else, for example.

但是,据我所知,由于.data(“events”)不是它们的公共API的一部分,如果所附加事件的底层表示从一个数组变化到另一个数组,那么对jQuery的更新可能会破坏您的代码。

Disclaimer: Since anything is possible :), here's your solution, but I would still err on the side of refactoring your existing code, as just trying to remember the order in which these items were attached can soon get out of hand as you keep adding more and more of these ordered events.

免责声明:因为一切皆有可能:),这是你的解决方案,但我仍然会宁可重构现有代码,只是想记得这些物品的顺序连接可以很快失控不断添加越来越多的命令事件。

#2


33  

You can do a custom namespace of events.

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

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

Then, when you need to trigger them you can choose the order.

然后,当您需要触发它们时,您可以选择顺序。

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

or

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

Also, just triggering click SHOULD trigger both in the order they were bound... so you can still do

而且,仅仅触发点击就应该按它们被绑定的顺序触发……你仍然可以这么做

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

#3


12  

A very good question ... I was intrigued so I did a little digging; for those who are interested, here's where I went, and what I came up with.

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

Looking at the source code for jQuery 1.4.2 I saw this block between lines 2361 and 2392:

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

There is a lot of interesting stuff going on here, but the part we are interested in is between lines 2384 and 2388:

这里有很多有趣的东西,但是我们感兴趣的部分在第2384行和第2388行之间:

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

Every time we call bind() or one() we are actually making a call to jQuery.event.add() ... so let's take a look at that (lines 1557 to 1672, if you are interested)

每次调用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 ...
    }

At this point I realized that understanding this was going to take more than 30 minutes ... so I searched * for

此时,我意识到理解这一点需要30分钟以上的时间……所以我搜索了*

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

and found this answer for iterating over bound events:

并找到了迭代绑定事件的答案:

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

    });

});

Testing that in Firefox I see that the events object in the data attribute of every element has a [some_event_name] attribute (click in our case) to which is attatched an array of handler objects, each of which has a guid, a namespace, a type, and a handler. "So", I think, "we should theoretically be able to add objects built in the same manner to the [element].data.events.[some_event_name].push([our_handler_object); ... "

在Firefox中进行测试时,我发现每个元素的data属性中的events对象都有一个[some_event_name]属性(在我们的例子中单击),它连接一个处理程序对象数组,每个对象都有一个guid、一个名称空间、一个类型和一个处理程序。“So”,我认为,“从理论上讲,我们应该能够向[element].data.events.[some_event_name].push([our_handler_object)添加以相同方式构建的对象;…”

And then I go to finish writing up my findings ... and find a much better answer posted by RusselUresti ... which introduces me to something new that I didn't know about jQuery (even though I was staring it right in the face.)

然后我去写我的发现…并找到一个更好的答案由RusselUresti发布…这给我带来了一些我不知道的关于jQuery的新东西(尽管我正盯着它看)。

Which is proof that * is the best question-and-answer site on the internet, at least in my humble opinion.

这证明*网站是互联网上最好的问答网站,至少在我看来是这样。

So I'm posting this for posterity's sake ... and marking it a community wiki, since RussellUresti already answered the question so well.

所以为了子孙后代着想,我把这个贴在网上……因为RussellUresti已经很好地回答了这个问题。

#4


4  

The standard principle is separate event handlers shouldn't depend upon the order they are called. If they do depend upon the order they should not be separate.

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

Otherwise, you register one event handler as being 'first' and someone else then registers their event handler as 'first' and you're back in the same mess as before.

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

#5


4  

.data("events") has been removed in versions 1.9 and 2.0beta, so you cant any longer rely on those solutions.

.data(“events”)已经在1.9和2.0beta版本中被删除,所以您不能再依赖这些解决方案了。

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

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

#6


3  

For jQuery 1.9+ as Dunstkreis mentioned .data('events') was removed. But you can use another hack (it is not recommended to use undocumented possibilities) $._data($(this).get(0), 'events') instead and solution provided by anurag will look like:

对于jQuery 1.9+,正如Dunstkreis提到的。data('events')被删除。但是您可以使用另一个hack(不建议使用未文档化的可能性)$._data($(this).get(0)、'events'), 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);
};

#7


3  

The selected answer authored by Anurag is only partially correct. Due to some internals of jQuery's event handling, the proposed bindFirst function will not work if you have a mix of handlers with and without filters (ie: $(document).on("click", handler) vs $(document).on("click", "button", handler)).

由Anurag编写的选定答案只是部分正确。由于jQuery事件处理的一些内部特性,如果您混合使用和不使用过滤器(例如:$(document))的处理程序,那么提议的bindFirst函数将无法工作。(“点击”,处理程序)和$(文档)。(“点击”、“按钮”,处理程序))。

The issue is that jQuery will place (and expect) that the first elements in the handler array will be these filtered handlers, so placing our event without a filter at the beginning breaks this logic and things start to fall apart. The updated bindFirst function should be as follows:

问题是,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);
    });
};

#8


1  

I'm assuming you are talking about the event bubbling aspect of it. It would be helpful to see your HTML for the said span elements as well. I can't see why you'd want to change the core behavior like this, I don't find it at all annoying. I suggest going with your second block of code:

我猜你说的是这个事件中冒泡的部分。查看您的HTML以获得上述span元素也是很有帮助的。我不明白你为什么要改变这种核心行为,我一点也不觉得它令人讨厌。我建议您使用第二段代码:

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

Most importantly I think you'll find it more organized if you manage all the events for a given element in the same block like you've illustrated. Can you explain why you find this annoying?

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

#9


1  

Here's a solution for jQuery 1.4.x (unfortunately, the accepted answer didn't work for jquery 1.4.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;
};

#10


1  

Chris Chilvers' advice should be the first course of action but sometimes we're dealing with third party libraries that makes this challenging and requires us to do naughty things... which this is. IMO it's a crime of presumption similar to using !important in CSS.

克里斯·奇尔弗斯的建议应该是行动的第一步,但有时我们面对的是第三方图书馆,这让我们面临挑战,要求我们做一些不正当的事情……这是。在我看来,这是一种类似于使用CSS的推定罪。

Having said that, building on Anurag's answer, here are a few additions. These methods allow for multiple events (e.g. "keydown keyup paste"), arbitrary positioning of the handler and reordering after the fact.

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

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

One could extrapolate on this with methods that would place a given handler before or after some other given handler.

我们可以用一些方法来推断这个问题,这些方法将在某个给定的处理程序之前或之后放置一个给定的处理程序。

#1


110  

Updated Answer

更新后的答案

jQuery changed the location of where events are stored in 1.8. Now you know why it is such a bad idea to mess around with internal APIs :)

jQuery更改了事件存储在1.8中的位置。现在您知道了为什么使用内部api是如此糟糕的想法了:)

The new internal API to access to events for a DOM object is available through the global jQuery object, and not tied to each instance, and it takes a DOM element as the first parameter, and a key ("events" for us) as the second parameter.

新的内部API可以通过全局jQuery对象访问DOM对象的事件,而不是绑定到每个实例,它将DOM元素作为第一个参数,键(对于我们来说是“events”)作为第二个参数。

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

So here's the modified code for jQuery 1.8.

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

And here's a playground.

这里有一个操场。


Original Answer

原来的答案

As @Sean has discovered, jQuery exposes all event handlers through an element's data interface. Specifically element.data('events'). Using this you could always write a simple plugin whereby you could insert any event handler at a specific position.

正如@Sean所发现的,jQuery通过元素的数据接口公开了所有事件处理程序。特别是element.data(“事件”)。使用它,您可以编写一个简单的插件,您可以在特定位置插入任何事件处理程序。

Here's a simple plugin that does just that to insert a handler at the beginning of the list. You can easily extend this to insert an item at any given position. It's just array manipulation. But since I haven't seen jQuery's source and don't want to miss out on any jQuery magic from happening, I normally add the handler using bind first, and then reshuffle the array.

下面是一个简单的插件,它可以在列表的开头插入一个处理程序。您可以很容易地将其扩展到在任何给定位置插入一个项目。它只是数组操作。但是由于我还没有看到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);
};

So for example, for this markup it would work as (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

However, since .data('events') is not part of their public API as far as I know, an update to jQuery could break your code if the underlying representation of attached events changes from an array to something else, for example.

但是,据我所知,由于.data(“events”)不是它们的公共API的一部分,如果所附加事件的底层表示从一个数组变化到另一个数组,那么对jQuery的更新可能会破坏您的代码。

Disclaimer: Since anything is possible :), here's your solution, but I would still err on the side of refactoring your existing code, as just trying to remember the order in which these items were attached can soon get out of hand as you keep adding more and more of these ordered events.

免责声明:因为一切皆有可能:),这是你的解决方案,但我仍然会宁可重构现有代码,只是想记得这些物品的顺序连接可以很快失控不断添加越来越多的命令事件。

#2


33  

You can do a custom namespace of events.

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

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

Then, when you need to trigger them you can choose the order.

然后,当您需要触发它们时,您可以选择顺序。

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

or

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

Also, just triggering click SHOULD trigger both in the order they were bound... so you can still do

而且,仅仅触发点击就应该按它们被绑定的顺序触发……你仍然可以这么做

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

#3


12  

A very good question ... I was intrigued so I did a little digging; for those who are interested, here's where I went, and what I came up with.

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

Looking at the source code for jQuery 1.4.2 I saw this block between lines 2361 and 2392:

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

There is a lot of interesting stuff going on here, but the part we are interested in is between lines 2384 and 2388:

这里有很多有趣的东西,但是我们感兴趣的部分在第2384行和第2388行之间:

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

Every time we call bind() or one() we are actually making a call to jQuery.event.add() ... so let's take a look at that (lines 1557 to 1672, if you are interested)

每次调用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 ...
    }

At this point I realized that understanding this was going to take more than 30 minutes ... so I searched * for

此时,我意识到理解这一点需要30分钟以上的时间……所以我搜索了*

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

and found this answer for iterating over bound events:

并找到了迭代绑定事件的答案:

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

    });

});

Testing that in Firefox I see that the events object in the data attribute of every element has a [some_event_name] attribute (click in our case) to which is attatched an array of handler objects, each of which has a guid, a namespace, a type, and a handler. "So", I think, "we should theoretically be able to add objects built in the same manner to the [element].data.events.[some_event_name].push([our_handler_object); ... "

在Firefox中进行测试时,我发现每个元素的data属性中的events对象都有一个[some_event_name]属性(在我们的例子中单击),它连接一个处理程序对象数组,每个对象都有一个guid、一个名称空间、一个类型和一个处理程序。“So”,我认为,“从理论上讲,我们应该能够向[element].data.events.[some_event_name].push([our_handler_object)添加以相同方式构建的对象;…”

And then I go to finish writing up my findings ... and find a much better answer posted by RusselUresti ... which introduces me to something new that I didn't know about jQuery (even though I was staring it right in the face.)

然后我去写我的发现…并找到一个更好的答案由RusselUresti发布…这给我带来了一些我不知道的关于jQuery的新东西(尽管我正盯着它看)。

Which is proof that * is the best question-and-answer site on the internet, at least in my humble opinion.

这证明*网站是互联网上最好的问答网站,至少在我看来是这样。

So I'm posting this for posterity's sake ... and marking it a community wiki, since RussellUresti already answered the question so well.

所以为了子孙后代着想,我把这个贴在网上……因为RussellUresti已经很好地回答了这个问题。

#4


4  

The standard principle is separate event handlers shouldn't depend upon the order they are called. If they do depend upon the order they should not be separate.

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

Otherwise, you register one event handler as being 'first' and someone else then registers their event handler as 'first' and you're back in the same mess as before.

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

#5


4  

.data("events") has been removed in versions 1.9 and 2.0beta, so you cant any longer rely on those solutions.

.data(“events”)已经在1.9和2.0beta版本中被删除,所以您不能再依赖这些解决方案了。

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

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

#6


3  

For jQuery 1.9+ as Dunstkreis mentioned .data('events') was removed. But you can use another hack (it is not recommended to use undocumented possibilities) $._data($(this).get(0), 'events') instead and solution provided by anurag will look like:

对于jQuery 1.9+,正如Dunstkreis提到的。data('events')被删除。但是您可以使用另一个hack(不建议使用未文档化的可能性)$._data($(this).get(0)、'events'), 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);
};

#7


3  

The selected answer authored by Anurag is only partially correct. Due to some internals of jQuery's event handling, the proposed bindFirst function will not work if you have a mix of handlers with and without filters (ie: $(document).on("click", handler) vs $(document).on("click", "button", handler)).

由Anurag编写的选定答案只是部分正确。由于jQuery事件处理的一些内部特性,如果您混合使用和不使用过滤器(例如:$(document))的处理程序,那么提议的bindFirst函数将无法工作。(“点击”,处理程序)和$(文档)。(“点击”、“按钮”,处理程序))。

The issue is that jQuery will place (and expect) that the first elements in the handler array will be these filtered handlers, so placing our event without a filter at the beginning breaks this logic and things start to fall apart. The updated bindFirst function should be as follows:

问题是,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);
    });
};

#8


1  

I'm assuming you are talking about the event bubbling aspect of it. It would be helpful to see your HTML for the said span elements as well. I can't see why you'd want to change the core behavior like this, I don't find it at all annoying. I suggest going with your second block of code:

我猜你说的是这个事件中冒泡的部分。查看您的HTML以获得上述span元素也是很有帮助的。我不明白你为什么要改变这种核心行为,我一点也不觉得它令人讨厌。我建议您使用第二段代码:

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

Most importantly I think you'll find it more organized if you manage all the events for a given element in the same block like you've illustrated. Can you explain why you find this annoying?

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

#9


1  

Here's a solution for jQuery 1.4.x (unfortunately, the accepted answer didn't work for jquery 1.4.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;
};

#10


1  

Chris Chilvers' advice should be the first course of action but sometimes we're dealing with third party libraries that makes this challenging and requires us to do naughty things... which this is. IMO it's a crime of presumption similar to using !important in CSS.

克里斯·奇尔弗斯的建议应该是行动的第一步,但有时我们面对的是第三方图书馆,这让我们面临挑战,要求我们做一些不正当的事情……这是。在我看来,这是一种类似于使用CSS的推定罪。

Having said that, building on Anurag's answer, here are a few additions. These methods allow for multiple events (e.g. "keydown keyup paste"), arbitrary positioning of the handler and reordering after the fact.

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

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

One could extrapolate on this with methods that would place a given handler before or after some other given handler.

我们可以用一些方法来推断这个问题,这些方法将在某个给定的处理程序之前或之后放置一个给定的处理程序。