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发现的,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); }; 

所以例如,对于这个标记,它会作为( 例子在这里 ):

 <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的更新可能会破坏你的代码。

免责声明:因为任何事情都是可能的:),这里是你的解决scheme,但是我仍然会在重构你现有的代码方面犯错误,因为试图记住这些项目的连接顺序很快就会失控,因为你继续添加越来越多的这些有序事件。

你可以做一个事件的自定义命名空间。

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

然后,当你需要触发他们,你可以select订单。

 $('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()我们实际上正在调用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多分钟…所以我search了Stackoverflow

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

在Firefox中testing我发现,每个元素的data属性中的events对象都有一个[some_event_name]属性( click我们的例子),其中有一个handler对象数组,每个handler对象都有一个guid,一个名称空间,一个types和处理程序。 “所以”,我想,“我们理论上应该能够以相同的方式添加对象到[element].data.events.[some_event_name].push([our_handler_object); …”

然后我去写完我的发现…并findRusselUresti发布的更好的答案…它向我介绍了一些我不了解jQuery的新东西(尽pipe我正在脸上盯着它) 。)

这是certificateStackoverflow是互联网上最好的问答网站,至less在我的愚见。

所以我为了后代的缘故发布这个…并且把它标记为一个社区wiki,因为RussellUresti已经很好地回答了这个问题。

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

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

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

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

对于jQuery 1.9+, Dunstkreis提到.data('events')被删除。 但是,您可以使用另一个hack(不build议使用未logging的可能性)$ ._ data($(this).get(0),'events'), anurag提供的解决scheme如下所示:

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

Anurag所选的答案只是部分正确的。 由于jQuery的事件处理的一些内部,如果你有一个处理程序有和没有filter的混合(即:$(document).on(“click”,handler)vs $(document).on,所build议的bindFirst函数将不起作用。 (“点击”,“button”,处理程序))。

问题是,jQuery将放置(并期望)处理程序数组中的第一个元素将是这些过滤处理程序,所以放置我们的事件在开始时没有filter打破了这个逻辑,事情开始崩溃。 更新的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); }); }; 

我假设你正在谈论它的事件冒泡方面。 看到你的HTML为span元素也是有帮助的。 我看不出为什么要改变这样的核心行为,我一点也不觉得烦。 我build议去你的第二块代码:

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

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

这里有一个jQuery 1.4.x解决scheme (不幸的是,接受的答案不适用于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; }; 

Chris Chilvers的build议应该是第一步,但有时候我们正在处理第三方库,这使得这个具有挑战性,并要求我们做顽皮的事情……这是什么。 国际海事组织是一种推定的罪行,类似于在CSS中使用!

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

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

人们可以用这个方法推断出一个给定的处理程序在给定的处理程序之前或之后。