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

asg*_*eo1 135 jquery

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

$('span').click(doStuff1);
$('span').click(doStuff2);
Run Code Online (Sandbox Code Playgroud)

单击跨度将导致doStuff1()触发,然后doStuff2().

在我绑定doStuff2()时,我想 doStuff1()之前选择绑定它,但似乎没有任何简单的方法来执行此操作.

我想大多数人会说,只需编写如下代码:

$('span').click(function (){
    doStuff2();
    doStuff1();
});
Run Code Online (Sandbox Code Playgroud)

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

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

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

Anu*_*rag 117

更新的答案

jQuery改变了1.8中存储事件的位置.现在你知道为什么乱搞内部API这是一个坏主意:)

用于访问DOM对象事件的新内部 API可通过全局jQuery对象获得,而不是绑定到每个实例,它将DOM元素作为第一个参数,并将一个键(我们的"事件")作为第二个参数.

jQuery._data(<DOM element>, "events");
Run Code Online (Sandbox Code Playgroud)

所以这里是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);
    });
};
Run Code Online (Sandbox Code Playgroud)

这是一个操场.


原始答案

正如@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);
};
Run Code Online (Sandbox Code Playgroud)

因此,例如,对于此标记,它将起作用(例如此处):

<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
Run Code Online (Sandbox Code Playgroud)

但是,.data('events')据我所知,因为它不是其公共API的一部分,所以如果附加事件的基础表示从数组更改为其他内容,jQuery的更新可能会破坏您的代码.

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

  • 你是对的,如果.data()不是API的一部分,那么使用它是一种风险.但是,我喜欢你的建议,将它的使用封装在一个新的jQuery函数中.至少如果.data()在以后的版本中断,你只需要更新一个函数 (4认同)
  • 要支持名称空间,请替换该行以获取处理程序:`var handlers = this.data('events')[name.split('.')[0]];` (4认同)
  • 从文档:调用jQuery.data(element)将所有元素的关联值检索为JavaScript对象.请注意,jQuery本身使用此方法存储**内部使用**的数据,例如**事件处理程序**,因此不要假设它只包含您自己的代码存储的数据. (3认同)
  • 好点..这样一来,改变将被隔离到一个功能. (2认同)

Rus*_*sti 33

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

$('span').bind('click.doStuff1',function(){doStuff1();});
$('span').bind('click.doStuff2',function(){doStuff2();});
Run Code Online (Sandbox Code Playgroud)

然后,当您需要触发它们时,您可以选择订单.

$('span').trigger('click.doStuff1').trigger('click.doStuff2');
Run Code Online (Sandbox Code Playgroud)

要么

$('span').trigger('click.doStuff2').trigger('click.doStuff1');
Run Code Online (Sandbox Code Playgroud)

此外,只需触发click按钮,它们按照绑定的顺序触发......所以你仍然可以

$('span').trigger('click'); 
Run Code Online (Sandbox Code Playgroud)

  • +1 你每天都会学到新东西。谢谢拉塞尔! (2认同)

Sea*_*ira 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;
    };
});
Run Code Online (Sandbox Code Playgroud)

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

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

每当我们打电话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 ...
    }
Run Code Online (Sandbox Code Playgroud)

在这一点上,我意识到理解这将花费超过30分钟......所以我搜索了Stackoverflow

jquery get a list of all event handlers bound to an element
Run Code Online (Sandbox Code Playgroud)

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

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

    });

});
Run Code Online (Sandbox Code Playgroud)

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

然后我去写完了我的发现......并找到很多更好的答案张贴RusselUresti ......,介绍我到新的东西,我不知道的jQuery(即使我盯着它的脸右侧.)

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

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


Chr*_*ers 5

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

否则,您将一个事件处理程序注册为“第一个”,然后其他人将其事件处理程序注册为“第一个”,然后您又会陷入与以前相同的混乱之中。


dsh*_*iro 5

Anurag 撰写的所选答案仅部分正确。由于 jQuery 事件处理的某些内部结构,如果您混合使用带过滤器和不带过滤器的处理程序(即:$(document).on("click", handler) 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);
    });
};
Run Code Online (Sandbox Code Playgroud)