如何排序与 jQuery 绑定的事件

假设我有一个 web 应用程序,它有一个页面,可能包含4个脚本块-我写的脚本可能在其中一个块中找到,但我不知道是哪一个,这是由控制器处理。

我将一些 onclick事件绑定到一个按钮上,但是我发现它们有时以我意想不到的顺序执行。

是否有办法确保秩序,或者您过去是如何处理这个问题的?

111263 次浏览

如果顺序很重要,可以创建自己的事件,并在其他回调触发这些事件时将回调绑定到触发。

$('#mydiv').click(function(e) {
// maniplate #mydiv ...
$('#mydiv').trigger('mydiv-manipulated');
});


$('#mydiv').bind('mydiv-manipulated', function(e) {
// do more stuff now that #mydiv has been manipulated
return;
});

至少是这样。

绑定回调的调用顺序由每个 jQuery 对象的事件数据管理。没有任何函数(据我所知)允许您直接查看和操作该数据,您只能使用 bind ()和 unbind ()(或任何等效的 helper 函数)。

Dowski 的方法是最好的,您应该修改各种绑定的回调函数,以绑定到一个有序的自定义事件序列,同时将“ first”回调函数绑定到“ real”事件。这样,不管它们以什么顺序绑定,序列都将以正确的方式执行。

我能看到的唯一选择是一些你真的,真的不想考虑的事情: 如果你知道函数的绑定语法可能在你之前就已经被绑定了,尝试解除所有这些函数的绑定,然后自己按照正确的顺序重新绑定它们。这是自找麻烦,因为现在您有了重复的代码。

如果 jQuery 允许您简单地更改对象事件数据中绑定事件的顺序,但是不需要编写一些似乎不可能的代码来挂接到 jQuery 核心,那就太酷了。我可能没想到会有这样的暗示,所以这可能是故意的疏忽。

你可以试试这样:

/**
* Guarantee that a event handler allways be the last to execute
* @param owner The jquery object with any others events handlers $(selector)
* @param event The event descriptor like 'click'
* @param handler The event handler to be executed allways at the end.
**/
function bindAtTheEnd(owner,event,handler){
var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
bindAtTheStart(owner,event,aux,true);


}
/**
* Bind a event handler at the start of all others events handlers.
* @param owner Jquery object with any others events handlers $(selector);
* @param event The event descriptor for example 'click';
* @param handler The event handler to bind at the start.
* @param one If the function only be executed once.
**/
function bindAtTheStart(owner,event,handler,one){
var eventos,index;
var handlers=new Array();
owner.unbind(event,handler);
eventos=owner.data("events")[event];
for(index=0;index<eventos.length;index+=1){
handlers[index]=eventos[index];
}
owner.unbind(event);
if(one){
owner.one(event,handler);
}
else{
owner.bind(event,handler);
}
for(index=0;index<handlers.length;index+=1){
owner.bind(event,ownerhandlers[index]);
}
}
function bindFirst(owner, event, handler) {
owner.unbind(event, handler);
owner.bind(event, handler);


var events = owner.data('events')[event];
events.unshift(events.pop());


owner.data('events')[event] = events;
}

Dowski 的方法是好的,如果你所有的回调函数总是存在,并且你很高兴它们相互依赖。

但是,如果希望回调彼此独立,则可以利用冒泡并将后续事件作为委托附加到父元素。父元素上的处理程序将在元素上的处理程序之后触发,一直到文档。这是相当好的,因为你可以使用 event.stopPropagation()event.preventDefault()等跳过处理程序,取消或取消的行动。

$( '#mybutton' ).click( function(e) {
// Do stuff first
} );


$( '#mybutton' ).click( function(e) {
// Do other stuff first
} );


$( document ).delegate( '#mybutton', 'click', function(e) {
// Do stuff last
} );

或者,如果你不喜欢这个,你可以使用 Nick Leach bindLast 插件来强制一个事件绑定到 last: https://github.com/nickyleach/jQuery.bindLast

或者,如果您正在使用 jQuery 1.5,您也可以使用新的 Deferred 对象进行一些巧妙的操作。

多年来,我一直试图推广这种过程,但在我的情况下,我只关心链中第一个事件侦听器的顺序。

如果有用的话,下面是我的 jQuery 插件,它绑定了一个事件侦听器,总是在其他任何事件之前被触发:

我不知道

(function($) {
$.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
var indexOfDot = eventType.indexOf(".");
var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";


eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
handler = handler == undefined ? eventData : handler;
eventData = typeof eventData == "function" ? {} : eventData;


return this.each(function() {
var $this = $(this);
var currentAttrListener = this["on" + eventType];


if (currentAttrListener) {
$this.bind(eventType, function(e) {
return currentAttrListener(e.originalEvent);
});


this["on" + eventType] = null;
}


$this.bind(eventType + eventNameSpace, eventData, handler);


var allEvents = $this.data("events") || $._data($this[0], "events");
var typeEvents = allEvents[eventType];
var newEvent = typeEvents.pop();
typeEvents.unshift(newEvent);
});
};
})(jQuery);

注意事项:

  • 这个还没有完全测试过。
  • 它依赖于 jQuery 框架不变的内部结构(只在1.5.2中测试过)。
  • 除了作为源元素的属性或使用 jQuery bind ()和其他相关函数之外,它不一定在以任何方式绑定的事件侦听器之前触发。

正常绑定处理程序,然后运行:

element.data('events').action.reverse();

例如:

$('#mydiv').data('events').click.reverse();

请注意,在 jQuery 环境中,这必须与版本1.8不同地实现。以下是来自 JQuery 博客:的发布说明

. data (“ events”) : jQuery 将其与事件相关的数据存储在一个数据对象中 在每个元素上命名(等待)事件。这是一个内部数据 因此在1.8中,这将从用户数据名称空间中删除 所以它不会与同名项冲突。 jQuery 的事件数据 仍然可以通过 jQuery._ data (元素,“ events”)访问

我们可以完全控制处理程序执行 在 jQuery 宇宙中的顺序。Ricoo 在上面指出了这一点。看起来他的回答并没有为他赢得很多爱,但是这个技巧非常方便。例如,考虑一下,任何时候您需要在库小部件中的某个处理程序之前执行您自己的处理程序,或者您需要有权力有条件地取消对小部件处理程序的调用:

$("button").click(function(e){
if(bSomeConditional)
e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
var aClickListeners = $._data(this, "events").click;
aClickListeners.reverse();
});

下面是我的尝试,涵盖了 jQuery 的不同版本:

// Binds a jQuery event to elements at the start of the event chain for that type.
jQuery.extend({
_bindEventHandlerAtStart: function ($elements, eventType, handler) {
var _data;


$elements.bind(eventType, handler);
// This bound the event, naturally, at the end of the event chain. We
// need it at the start.


if (typeof jQuery._data === 'function') {
// Since jQuery 1.8.1, it seems, that the events object isn't
// available through the public API `.data` method.
// Using `$._data, where it exists, seems to work.
_data = true;
}


$elements.each(function (index, element) {
var events;


if (_data) {
events = jQuery._data(element, 'events')[eventType];
} else {
events = jQuery(element).data('events')[eventType];
}


events.unshift(events.pop());


if (_data) {
jQuery._data(element, 'events')[eventType] = events;
} else {
jQuery(element).data('events')[eventType] = events;
}
});
}
});

JQuery 1.5引入了承诺,这是我见过的控制执行顺序的最简单的实现

$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );

在一些特殊的情况下,当你不能改变点击事件的绑定方式(事件绑定来自其他代码) ,你可以改变 HTML 元素,这里有一个可能的解决方案(警告: 这是 没有推荐的绑定事件的方式,其他开发人员可能会杀了你) :

<span onclick="yourEventHandler(event)">Button</span>

通过这种绑定方式,事件处理程序将首先被添加,因此它将首先被执行。

我也有同样的问题,找到了这个话题。以上的答案可以解决这些问题,但我不认为他们是好的计划。

让我们想想现实世界。

如果我们使用这些答案,我们必须改变我们的代码。你必须改变你的代码风格。像这样:

原创:

$('form').submit(handle);

黑客:

bindAtTheStart($('form'),'submit',handle);

随着时间的推移,想想你的项目。代码丑陋且难以阅读!另一个原因是简单的总是更好的。如果您有10个 bindAtTheStart,它可能没有 bug。如果您有100 bindAtTheStart,您真的确定可以保持它们的正确顺序吗?

因此,如果您必须绑定相同的事件多个。我认为最好的方法是控制 js-file 或 js-code 加载顺序。Jquery 可以将事件数据作为队列处理。顺序是先进先出。你不需要修改任何代码。改变装载顺序。