这个时候假设又新添了一个事件绑定:addEvent(dom,"focus",f3);那么element.events.focus[3] = f3;这也是对象相比于数组的方便之处,数组不可能没有下标0,1,2就直接有3了,但是对象却可以,此时3是作为对象的一个属性名称。
这样的设计,其实已经具备了jquery事件系统的雏形,包含了几个最主要的特点:
1)element上的所有事件,将保存到element.events属性中,不是直接绑定到element上;这样一个事件可以有无数个响应函数。
2)handleEvent作为element所有事件的“主监听函数”,有它统一管理element上的所有函数。
3)所有浏览器都支持element["on" + type]事件绑定方式,跨浏览器兼容。
好啦,明白了addEvent的事件结构,这个想法确实让人觉得眼前一亮。下面分析jQuery的事件结构
b. jQuery的事件结构
所有的函数添加事件都会进入jQuery.event.add函数。该函数有两个主要功能:添加事件、附加很多事件相关信息。我们直接上源码,源码思想和Dean Edwards的跨浏览器兼容事件添加处理类似。
源码分析
add: function( elem, types, handler, data, selector ) { var tmp, events, t, handleObjIn, special, eventHandle, handleObj, handlers, type, namespaces, origType, //获取elem节点对应的缓存数据 elemData = jQuery._data( elem ); //没有数据或文本/注释节点不能附加事件(但是允许附加普通对象) if ( !elemData ) { return; } //调用者能通过自定义数据替换handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } //确保handler函数有唯一的ID,后续会用来查找/删除这个handler函数 if ( !handler.guid ) { handler.guid = jQuery.guid++; } //如果是初次进入,初始化元素的事件结构和主事件响应入口 if ( !(events = elemData.events) ) { events = elemData.events = {}; } if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { //当一个事件被调用后页面已经卸载,则放弃jQuery.event.trigger()的第二个事件, return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露 eventHandle.elem = elem; } //多个事件使用空格隔开的处理 //如jQuery(...).bind("mouseover mouseout", fn); //core_rnotwhite = /\S+/g;匹配空白字符 types = ( types || "" ).match( core_rnotwhite ) || [""]; t = types.length; while ( t-- ) { //rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; //获取命名空间和原型事件 tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); //如果事件改变其类型,使用special事件处理器来处理更改后的事件类型 special = jQuery.event.special[ type ] || {}; //如果选择器已定义,确定special事件API类型,否则给他一个类型 type = ( selector ? special.delegateType : special.bindType ) || type; //基于新设置的类型更新special special = jQuery.event.special[ type ] || {}; // handleObj贯穿整个事件处理 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, // For use in libraries implementing .is(). We use this for POS matching in `select` //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + //whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) //用来判断亲密关系 needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn ); //初次使用时初始化事件处理器队列 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; //非自定义事件,如果special事件处理器返回false,则只能使用addEventListener/attachEvent 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; } } //将事件对象handleObj添加到元素的处理列表,代理计数递增 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } //跟踪那个事件曾经被使用过,用于事件优化 jQuery.event.global[ type ] = true; } //防止ie内存泄漏 elem = null; }
依然用实例来说明jQuery的事件结构