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的更新可能会破坏您的代码.
免责声明:因为一切皆有可能:),这是你的解决方案,但我仍然会在重构现有代码方面犯错误,因为只要记住这些项目附加的顺序很快就会失控,因为你不断添加这些有序事件越来越多.
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)
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中我看到每个元素events的data属性中的对象都有一个[some_event_name]属性(click在我们的例子中)附加了一个handler对象数组,每个对象都有一个guid,一个命名空间,一个类型和一个处理程序."那么",我认为,"理论上我们应该能够以相同的方式添加对象[element].data.events.[some_event_name].push([our_handler_object);......"
然后我去写完了我的发现......并找到很多更好的答案张贴RusselUresti ......,介绍我到新的东西,我不知道的jQuery(即使我盯着它的脸右侧.)
这证明Stackoverflow是互联网上最好的问答网站,至少在我的拙见中.
所以我发布这个是为了后人的缘故......并将其标记为社区维基,因为RussellUresti已经很好地回答了这个问题.
标准原则是单独的事件处理程序不应依赖于它们的调用顺序。如果它们确实依赖于顺序,则它们不应该分开。
否则,您将一个事件处理程序注册为“第一个”,然后其他人将其事件处理程序注册为“第一个”,然后您又会陷入与以前相同的混乱之中。
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)