jq源码 | 2020-12-28 15:59:19 320次 0次
事件入口
jq 提供的事件操作 api 很多,但是基本都是依赖 on 方法为入口进行处理。
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup contextmenu" ).split( " " ), function( _i, name ) { jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; } );
其他方法:
jQuery.fn.extend( { on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, ... off: function( types, selector, fn ) { ... return this.each( function() { jQuery.event.remove( this, types, fn, selector ); } ); } } );
jQuery.fn.extend( { trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, ... } );
下面介绍一个 click 事件的执行流程:
on
|
jQuery.event.add
|
jQuery.event.dispatch
|
jQuery.event.fix
|
jQuery.event.handlers
|
run callback
on
function on( elem, types, selector, data, fn, one ) { ... return elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); }
jQuery.event.add
事件的添加和缓存,不会直接给 dom 添加事件:
jQuery.event = { add: function( elem, types, handler, data, selector ) { ... // 缓存中取 elemData = data_priv.get( elem ); ... // 事件标识 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // 同一个dom中的多个事件,避免重复初始化 if ( !( events = elemData.events ) ) { events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // !!!!!!事件监听被触发 return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } ... while ( t-- ) { ... 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 ); ... // handlers \ events \ elemData 有引用 handlers = events[ type ] = []; ... // !!!!!监听,点击时执行 jQuery.event.dispatch elem.addEventListener( type, eventHandle ); ... ... if ( selector ) { // 元素被委托事件 // 并且委托元素事件排序在普通事件的前面 handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } } }, }
jQuery.event.dispatch
上面的过程中完成了事件的处理和绑定,给元素绑定了事件监听,但监听的事件不是直接执行传入的事件回调,而是一个jq内部中的 dispatch,这里做三件事:
修正 event(目前看感觉没有必要)
解析出 handlerQueue 事件集合
执行
dispatch: function( nativeEvent ) { ... event = jQuery.event.fix( nativeEvent ), ... // 解析 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // 执行 判断阻止冒泡 i = 0; while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { ... // 执行传入的回调函数 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(); } } } } } ... return event.result; },
以上是一个基本的事件注册和执行流程,下面介绍一下事件委托的设计。
事件委托
比如一些动态创建的节点,直接绑定事件是无效的,因为 dom 没有渲染,jq 的选择器找不到,只能使用事件委托的形势,因为事件委托是一个事件监听父元素的点击事件,通过 target 判断进行回调方法的执行,有关委托的处理在 jQuery.event.handlers 里:
handlers: function( event, handlers ) { ... // target获取 点击的元素 cur = event.target; // 委托事件 delegateCount 在add方法中 判断如果事件中传入了一个选择器(被委托)则进行++处理 if ( delegateCount ) { // cur !== this 判断点击的元素和委托的元素是否相同 // 并且一直往上找 直到找到被委托的目标 for ( ; cur !== this; cur = cur.parentNode || this ) { if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; // 委托事件个数 for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // 选择器 sel = handleObj.selector + " "; // 选择dom if ( matchedSelectors[ sel ] === undefined ) { matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } // 事件回调函数存储 if ( matchedSelectors[ sel ] ) { matchedHandlers.push( handleObj ); } } if ( matchedHandlers.length ) { handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // 普通的事件 cur = this; if ( delegateCount < handlers.length ) { handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; },
大致流程还是比较清晰,其中需要注意的是,在 add 方法中 handlers 进行事件存储时,事件委托的顺序是在普通事件之前的,这样做的好处就是默认事件就冒泡了,不需要其他的处理,如下:
$('.par').click(() => { console.log(222222) }) $('.par').on('click', '.child', (e) => { console.log(111111) e.stopPropagation(); })
情况一:不加组织冒泡事件,结果为 111111 再是 222222
情况二:加上组织冒泡,由于事件委托事件排序在前,先执行,执行完,e对象中加了 stopPropagation,这个 e 是 jq 内部中共用的一个对象,所以下个事件执行时会判断 event.isPropagationStopped() 值,决定是否继续执行,这样就成功的阻止了冒泡。
事件系统的大体设计流程结束,其他的还有很多细节处理,这里不再展开。
0人赞