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兼容等却不会消逝。
好了,差不多就是这些了。
- jQuery-事件体系和回调系统
- nginx 工作原理,进程模型,事件处理,配置系统和模块化体系
- jQuery动画和事件
- jQuery-事件和应用
- [jQuery] 选择器和事件
- jQuery选择器和事件
- jQuery选择器和事件
- JQuery 选择器和事件
- jQuery选择器和事件
- jQuery -- 选择器和事件
- jquery事件和动画
- jQuery选择器和事件
- jquery事件和动画
- jquery事件和动画
- jQuery事件和动画
- 信息安全系统和安全体系
- android View的事件体系 scrollTo和scrollBy的区别
- jquery绑定事件和去除事件绑定
- 大型网站子系统简介
- 打印机配置问题解决小记 it is not equipped with the paper skip bin
- GitHub撤销修改
- JavaEE_Mybatis_SpringMVC_ Springmvc 数据回显 通过 ModelAndView向前台传值
- 小知识点3
- jQuery-事件体系和回调系统
- ios读书笔记
- 天气预报接口api(中国天气网)
- JS中showModalDialog 详细使用
- 深入理解 Java中的 流 (Stream)
- 设计模式之工厂方法模式
- Http协议对于java和asp.net的异同
- Ecliplse 指定JRE
- C++ 14观察者模式