说起jQuery的事件,不得不提一下Dean Edwards大神 addEvent库,很多流行的类库的基本思想从他那儿借来的 jQuery的事件处理机制吸取了JavaScript专家Dean Edwards编写的事件处理函数的精华,使得jQuery处理事件绑定的时候相当的可靠。 在预留退路(graceful degradation),循序渐进以及非入侵式编程思想方面,jQuery也做的非常不错 事件的流程图 总的来说对于JQuery的事件绑定 在绑定的时候做了包装处理 在执行的时候有过滤器处理
.on( events [, selector ] [, data ], handler(eventObject) ) events:事件名 selector : 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素 data :当一个事件被触发时,要传递给事件处理函数的 handler:事件被触发时,执行的函数 例如: var body = $('body') body.on('click','p',function(){ console.log(this) }) 用on方法给body上绑定一个click事件,冒泡到p元素的时候才出发回调函数 这里大家需要明确一点:每次在body上点击其实都会触发事件,但是只目标为p元素的情况下才会触发回调handler 通过源码不难发现,on方法实质只完成一些参数调整的工作,而实际负责事件绑定的是其内部jQuery.event.add方法 jQuery.onon: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); });
针对事件处理,我们可以拆分2部分: 一个事件预绑定期 一个事件执行期 本章着重讲解事件的预绑定的时候做了那些处理,为什么要这样处理? 事件底层的绑定接口无非就是用addEventListener处理的,所以我们直接定位到addEventListener下面 jQuery.event.add 中有 elem: 目标元素 type: 事件类型,如’click’ eventHandle: 事件句柄,也就是事件回调处理的内容了 false: 冒泡
现在我们把之前的案例给套一下看看 var body = document.getElementsByTagName('body') var eventHandle = function(){ console.log(this) } body .addEventListener( 'click’, eventHandle, false ); 明显有问题,每次在body上都触发了回调,少了个p元素的处理,当然这样的效果也无法处理
eventHandle源码 回到内部绑定的事件句柄eventHandle ,可想而知eventHandle不仅仅只是只是充当一个回调函数的角色,而是一个实现了EventListener接口的对象 if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events eventHandle.elem = elem; } 可见在eventHandle中并没有直接处理回调函数,而是映射到jQuery.event.dispatch分派事件处理函数了 仅仅只是传入eventHandle.elem,arguments , 就是body元素 与事件对象 那么这里有个问题,事件回调的句柄并没有传递过去,后面的代码如何关联? 本章的一些地方可能要结合后面的dispatch处理才能理清,但是我们还是先看看做了那些处理
on内部的实现机制 我们开从头来理清下jQuery.event.add代码结构,适当的跳过这个环节中不能理解的代码,具体遇到在提出 之前就提到过jQuery从1.2.3版本引入数据缓存系统,贯穿内部,为整个体系服务,事件体系也引入了这个缓存机制 所以jQuery并没有将事件处理函数直接绑定到DOM元素上,而是通过$.data存储在缓存$.cahce上
第一步:获取数据缓存 //获取数据缓存 elemData = data_priv.get( elem ); 在$.cahce缓存中获取存储的事件句柄对象,如果没就新建elemData
第二步:创建编号 if ( !handler.guid ) { handler.guid = jQuery.guid++; } 为每一个事件的句柄给一个标示,添加ID的目的是 用来寻找或者删除handler,因为这个东东是缓存在缓存对象上的,没有直接跟元素节点发生关联
第三步:分解事件名与句柄 if ( !(events = elemData.events) ) { events = elemData.events= {}; } if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; eventHandle.elem = elem; } events,eventHandle 都是elemData缓存对象内部的,可见 在elemData中有两个重要的属性, 一个是events,是jQuery内部维护的事件列队 一个是handle,是实际绑定到elem中的事件处理函数 之后的代码无非就是对这2个对象的筛选,分组,填充了
第四步: 填充事件名与事件句柄 // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); // 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组 // core_rnotwhite:/\S+/g types = ( types || "" ).match( core_rnotwhite ) || [""]; // 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"] // 事件的个数 t = types.length; while ( t-- ) { // 尝试取出事件的命名空间 // 如"mouseover.a.b" → ["mouseover.a.b", "mouseover", "a.b"] tmp = rtypenamespace.exec( types[t] ) || []; // 取出事件类型,如mouseover type = origType = tmp[1]; // 取出事件命名空间,如a.b,并根据"."分隔成数组 namespaces = ( tmp[2] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type // 事件是否会改变当前状态,如果会则使用特殊事件 special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type // 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type // type状态发生改变,重新定义特殊事件 special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers // 这里把handleObj叫做事件处理对象,扩展一些来着handleObjIn的属性 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 ); // Init the event handler queue if we're the first // 初始化事件处理列队,如果是第一次使用,将执行语句 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false // 如果获取特殊事件监听方法失败,则使用addEventListener进行添加事件 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } } } // 特殊事件使用add处理 if ( special.add ) { special.add.call( elem, handleObj ); // 设置事件处理函数的ID if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front // 将事件处理对象推入处理列表,姑且定义为事件处理对象包 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization // 表示事件曾经使用过,用于事件优化 jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE // 设置为null避免IE中循环引用导致的内存泄露 elem = null; }, 这段比较长了分解下,最终的目的就是为填充events,eventHandle
涉及
多事件处理: 如果是多事件分组的情况jQuery(...).bind("mouseover mouseout", fn); 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组
增加命名空间处理: 事件名称可以添加指定的event namespaces(命名空间) 来简化删除或触发事件。例如,
引入jQuery的Special Event机制 什么时候要用到自定义函数?有些浏览器并不兼容某类型的事件,如IE6~8不支持hashchange事件,你无法通过jQuery(window).bind('hashchange', callback)来绑定这个事件,这个时候你就可以通过jQuery自定义事件接口来模拟这个事件,做到跨浏览器兼容。 原理 jQuery(elem).bind(type, callbakc)实际上是映射到 jQuery.event.add(elem, types, handler, data)这个方法,每一个类型的事件会初始化一次事件处理器,而传入的回调函数会以数组的方式缓存起来,当事件触发的时候处理器将依次执行这个数组。 !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false jQuery.removeEvent( elem, type, elemData.handle );
jQuery.event.special对象中,保存着为适配特定事件所需的变量和方法, 具体有: 在适配工作完成时,会产生一个handleObj对象,这个对象包含了所有在事件实际被触发是所需的所有参数
采用自定义事件或者浏览器接口绑定事件 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } }
冒泡标记 handlers.splice( handlers.delegateCount++, 0, handleObj );
最后记得 设置为null避免IE中循环引用导致的内存泄露 elem = null;
这个元素没有直接让事件直接引用了,而是挂在到,数据缓存句柄上,很好的避免了这个IE泄露的问题 eventHandle.elem = elem;
通过整个流程,我们的数据缓存对象就填充完毕了,看看截图
events:handleObj
handle
数据缓存对象
得出总结: 在jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : 方法中没有传递回调对象 是因为回调的句柄被关联到了elemData,也就是内部数据缓存中了
不难得出jQuery的事件绑定机制: jQuery对每一个elem中的每一种事件,只会绑定一次事件处理函数(绑定这个elemData.handle), 而这个elemData.handle实际只做一件事,就是把event丢到jQuery内部的事件分发程序
jQuery.event.dispatch.apply( eventHandle.elem, arguments ); 而不同的事件绑定,具体是由jQuery内部维护的事件列队来区分(就是那个elemData.events) 在elemData中获取到events和handle之后,接下来就需要知道这次绑定的是什么事件了
画了个简单流程图
本章只是事件的前段部分,解析完elemData数据,在执行期间又会如何处理,如何巧妙的运用这些设计,下章分晓! |
|
来自: 昵称10504424 > 《工作》