jQuery-事件体系和回调系统

6 篇文章 0 订阅

um..最近太忙了,这篇博客早在几个月前就想写来着,现在终于抽出时间来,另外应该是jQuery库的最后一篇分析了(也说不定哪天再写下jQuery的DOM选择器引擎)。

anyway,切入主题:

method shortcut:

// Return jQuery for attributes-only inclusion

jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
});

遍历数组添加快捷方法是jQuery常用的一种方式,根据参数长度来判断是绑定事件还是触发事件。先看事件绑定:

on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
	var type, origFn;
	if ( typeof types === "object" ) {
		if ( typeof selector !== "string" ) {
			data = data || selector;
			selector = undefined;
		}
		for ( type in types ) {
			this.on( type, selector, data, types[ type ], one );
		}
		return this;
	}

	if ( data == null && fn == null ) {
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {
		if ( typeof selector === "string" ) {
			fn = data;
			data = undefined;
		} else {
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return this;
	}

	if ( one === 1 ) {
		origFn = fn;
		fn = function( event ) {
			jQuery().off( event );
			return origFn.apply( this, arguments );
		};
		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
	}
	return this.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	});
}

on方法内部调用的实际是jQuery.event.add,on方法提供的只是多种方式的接口调用方式。

插嘴一句:jQuery处理参数大多是扁平化、统一化的方式,因为很多参数的可选的,所以会有很多交换参数位置的代码逻辑出现。


jQuery.event.add:

add: function( elem, types, handler, data, selector ) {
	var tmp, events, t, handleObjIn,
		special, eventHandle, handleObj,
		handlers, type, namespaces, origType,
		elemData = jQuery._data( elem );		
	if ( !elemData ) {
		return;
	}		
	if ( handler.handler ) {
		handleObjIn = handler;
		handler = handleObjIn.handler;
		selector = handleObjIn.selector;
	}		
	if ( !handler.guid ) {
		handler.guid = jQuery.guid++;
	}		
	if ( !(events = elemData.events) ) {
		events = elemData.events = {};
	}
	if ( !(eventHandle = elemData.handle) ) {
		eventHandle = elemData.handle = function( e ) {
			
			
			return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
				jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
				undefined;
		};
		
		eventHandle.elem = elem;
	}		
	types = ( types || "" ).match( rnotwhite ) || [ "" ];
	t = types.length;
	while ( t-- ) {
		tmp = rtypenamespace.exec( types[t] ) || [];
		type = origType = tmp[1];
		namespaces = ( tmp[2] || "" ).split( "." ).sort();			
		if ( !type ) {
			continue;
		}			
		special = jQuery.event.special[ type ] || {};			
		type = ( selector ? special.delegateType : special.bindType ) || type;			
		special = jQuery.event.special[ type ] || {};			
		handleObj = jQuery.extend({
			type: type,
			origType: origType,
			data: data,
			handler: handler,
			guid: handler.guid,
			selector: selector,
			needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
			namespace: namespaces.join(".")
		}, handleObjIn );			
		if ( !(handlers = events[ type ]) ) {
			handlers = events[ type ] = [];
			handlers.delegateCount = 0;				
			if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
				
				if ( elem.addEventListener ) {
					elem.addEventListener( type, eventHandle, false );					} else if ( elem.attachEvent ) {
					elem.attachEvent( "on" + type, eventHandle );
				}
			}
		}			if ( special.add ) {
			special.add.call( elem, handleObj );				if ( !handleObj.handler.guid ) {
				handleObj.handler.guid = handler.guid;
			}
		}			
		if ( selector ) {
			handlers.splice( handlers.delegateCount++, 0, handleObj );
		} else {
			handlers.push( handleObj );
		}			
		jQuery.event.global[ type ] = true;
	}		
	elem = null;
}
暂时先不分析源码,先提一个问题:

Callback system(回调系统) VS event dispatch(事件分发)

首先看回调系统有什么问题:

回调系统的实现方式很简单,大多都是将一组回调函数放在一个数组中,然后遍历执行。

那么很严重的问题是:

当一个回调函数抛出异常时,剩余的回调函数将不会被执行。

try/catch ?

try/catch当然可以,不过你就看不到本应该抛出的异常了。

再看看事件分发的问题:

慢!对,就是慢。简单的往数组中push一个func比重复调用添加事件的API要快得多。


所以,解决方案是是什么:

有兴趣的可以看下Dean Edwards的blog

个人的建议是使用try/catch结合setTimeout将异常抛出。(这个问题欢迎大家探讨)

setTimeout仍然存在一下问题:

1、性能问题,如果是一个图标的库,或者是mousemove这样的事件,那么setTimeout会很大程度上影响性能

2、debug,使用setTimeout导致无法debug了,丢失了调用栈。如果你的应用有自动上报错误的功能,那么这种错误上报上去会一点帮助也没有。

那么这里再提出两个方案:

1、简单的console.error()打印错误

2、使用一个数组来收集这些错误信息,然后统一处理或者上报。


回归正题:

jQuery的事件体系明显是用回调系统实现的,因为回调系统可以应用在任何对象上,随之带来的好处是你有了一个绑定/触发的事件系统。

add方法:

1、将回调函数push到handlers中

2、绑定handle处理函数到对象/元素上

3、根据selector判断是否是事件委托,使用handlers.delegateCount记录该对象的事件委托数量

4、对special事件做特殊处理

好处是同一事件只绑定一次,没有产生闭包变量引用(给handle处理函数添加了elem元素属性),不会导致内存泄露。


再看事件分发:

dispatch: function( event ) {

	// Make a writable jQuery.Event from the native event object
	event = jQuery.event.fix( event );

	var i, ret, handleObj, matched, j,
		handlerQueue = [],
		args = slice.call( arguments ),
		handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
		special = jQuery.event.special[ event.type ] || {};

	// Use the fix-ed jQuery.Event rather than the (read-only) native event
	args[0] = event;
	event.delegateTarget = this;

	// Call the preDispatch hook for the mapped type, and let it bail if desired
	if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
		return;
	}

	// Determine handlers
	handlerQueue = jQuery.event.handlers.call( this, event, handlers );

	// Run delegates first; they may want to stop propagation beneath us
	i = 0;
	while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
		event.currentTarget = matched.elem;

		j = 0;
		while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {

			// Triggered event must either 1) have no namespace, or
			// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
			if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

				event.handleObj = handleObj;
				event.data = handleObj.data;

				ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
						.apply( matched.elem, args );

				if ( ret !== undefined ) {
					if ( (event.result = ret) === false ) {
						event.preventDefault();
						event.stopPropagation();
					}
				}
			}
		}
	}

	// Call the postDispatch hook for the mapped type
	if ( special.postDispatch ) {
		special.postDispatch.call( this, event );
	}

	return event.result;
}

首先重写了event:event = jQuery.event.fix( event );

jQuery.event.fix方法在这里就不分析了(jQuery事件体系太庞大了,不可能所有的都做分析。嗯,我就是懒。)

然后确定了handlerQueue:handlerQueue = jQuery.event.handlers.call( this, event, handlers );(嗯,handlers方法也不分析。)

最后遍历执行回调列表(其中又包含了对special、事件传播、阻止默认事件的处理)。


再看事件触发:

jQuery的事件触发很复杂,很简单的Callbacks系统不一样,jQuery的事件体系,因为模拟了标准的DOM事件流程,所以一个事件还会进行冒泡。

所以trigger会在需要冒泡的时候(!onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ))进行获取到当前节点的事件冒泡路径。然后遍历路径检测改路径上是否注册了该事件的回调,如果有则执行,一直到冒泡结束或事件传播被stop时停下。最后判断该节点是否被阻止默认事件,如果不是则执行原生的DOM method。


事件的移除就不说了,原理就是从回调列表中删掉而已,唯一复杂的是控制委托数目和对特殊事件的处理。


事件作为DOM非常重要的一部分,怎么优化事件处理,提供优雅的API,适应更多的复杂情况,jQuery在这方面做得都很好,虽然jQuery渐渐退出前端的舞台,但其中的设计思路,Hack兼容等却不会消逝。


好了,差不多就是这些了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值