下面的示例显示了两种常见的现代方法,用于在自定义JavaScript对象中提供事件处理。 (我认为。)该示例基于通过AJAX在jQuery中加载数据的常见场景,其中传递的参数也需要在最终完成的事件中提供。
CustomClass.prototype.loadData(param1) {
var thisObj = this;
$.ajax({
url : this.reqUrl,
context: this,
// There are various ways to do this, including writing the handlers inline
// or using $.proxy. Shouldn't be important for this question.
success : function(dataResp, textStatus) {thisObj.onSuccess(dataResp,
textStatus,
param1)}
});
};
// Manage listeners. Storing functions here, but
// could also use objects with predefined functions.
CustomClass.prototype.addListener(callbackFunc) {
this.listeners.Add(callbackFunc);
}
CustomClass.prototype.onSuccess(dataResp, textStatus, param1) {
//**OPTION 1**
// Publish event via callbacks/listeners
for(var i=0; i<this.listeners; i++)
this.listeners[i](dataResp, param1);
//**OPTION 2**
// Publish event via trigger/on
$(this).trigger('my-event-name', [dataResp, param1]);
};
以下显示了这两个选项在代码中的外观:
function handler(dataResp, param1) {
// do work, param1 == 'whatever'
}
c1 = new CustomClass();
c1.addListener(handler);
c1.loadData('whatever');
c2 = new CustomClass();
$(c2).on('my-event-name', handler);
c2.loadData('whatever');
结果与上述任一选项相同。使用jquery的on / trigger支持消除了管理监听器的代码,但是:
关于使用DOM元素使用事件监听器样式编程的好处,关于SO有很多问题/答案。关于在非DOM对象中提供事件处理的各种方法,有很多问题/答案。但是,对于非DOM对象的首选方法和潜在的权衡,我找不到任何明确的权利,特别是在非平凡的用例中,例如上面的例子。
EDIT ...当然,在发布之后,我看到this question/answer来了(非常)接近。但是,除了向自定义对象添加其他属性外,它不表示是否还有其他任何分支。另外,我相信我的问题提供了一个更清晰的用例。
答案 0 :(得分:0)
回答我自己的问题:
最好在编写自定义对象时使用回调,这些对象不会生成或消耗事件作为DOM交互的一部分。使用jQuery的on / trigger事件处理会继承许多额外的代码路径,因为它会挂钩到jQuery的大型事件处理子系统。一些逻辑用于处理浏览器差异。使用附加代码是因为它重用和/或复制了Web浏览器中的事件处理功能,但是在跨浏览器上下文中。
相比之下,使用自定义回调处理是微不足道的。 Jquery还提供$ .Callbacks函数,可以节省一些编码,并可选择包含更高级的回调功能。
对这个问题的更完整答案将提供各种方法的时序分析。然而,仅jquery v1.9.1的来源提供了强有力的轶事证据:
jQuery的触发方法:
trigger: function( event, data, elem, onlyHandlers ) {
var handle, ontype, cur,
bubbleType, special, tmp, i,
eventPath = [ elem || document ],
type = core_hasOwn.call( event, "type" ) ? event.type : event,
namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
cur = tmp = elem = elem || document;
// Don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// focus/blur morphs to focusin/out; ensure we're not firing them right now
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
return;
}
if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
ontype = type.indexOf(":") < 0 && "on" + type;
// Caller can pass in a jQuery.Event object, Object, or just an event type string
event = event[ jQuery.expando ] ?
event :
new jQuery.Event( type, typeof event === "object" && event );
event.isTrigger = true;
event.namespace = namespaces.join(".");
event.namespace_re = event.namespace ?
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
null;
// Clean up the event in case it is being reused
event.result = undefined;
if ( !event.target ) {
event.target = elem;
}
// Clone any incoming data and prepend the event, creating the handler arg list
data = data == null ?
[ event ] :
jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
return;
}
// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
bubbleType = special.delegateType || type;
if ( !rfocusMorph.test( bubbleType + type ) ) {
cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
eventPath.push( cur );
tmp = cur;
}
// Only add window if we got to document (e.g., not plain obj or detached DOM)
if ( tmp === (elem.ownerDocument || document) ) {
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
i = 0;
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
// Native handler
handle = ontype && cur[ ontype ];
if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
event.preventDefault();
}
}
event.type = type;
// If nobody prevented the default action, do it now
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
try {
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
}
return event.result;
}
有关其他信息,请参阅@ kevin-b对此问题的评论,以及this older question/answer。