jQuery-事件体系和回调系统

来源:互联网 发布:一个域名指向多个ip 编辑:程序博客网 时间:2024/06/04 19:00

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

anyway,切入主题:

method shortcut:

// Return jQuery for attributes-only inclusionjQuery.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 bindingjQuery.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 objectevent = 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 eventargs[0] = event;event.delegateTarget = this;// Call the preDispatch hook for the mapped type, and let it bail if desiredif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {return;}// Determine handlershandlerQueue = jQuery.event.handlers.call( this, event, handlers );// Run delegates first; they may want to stop propagation beneath usi = 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 typeif ( 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 0
原创粉丝点击