jquery源码分析之jQuery事件绑定

当使用类似以下代码时,

$(function(){ 
			$("#hr_three").click(function(event){ 
				alert('阻止时间冒泡');
				event.stopPropagation(); 
			}); 
		});
<form id="form1"> 
<div id="divOne" onclick="alert('我是最外层');"> 
<div id="divTwo" onclick="alert('我是中间层!')"> 
<a id="hr_three" onclick="alert('我是最里层!')">点击我</a> 
</div>
</div> 
</form>

jquery会将$("#hr_three")元素与click事件进行绑定,首先jquery会进行事件注册,如下代码:

// 对以下所有事件进行绑定
jQuery.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 binding
    // 将事件属性添加到jQuery对象
	jQuery.fn[ name ] = function( data, fn ) {
        // 根据调用参数数量进行不同调用,本例中调用this.on方法,也就是jQuery对象的on方法
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
} );
// 对jQuery.fn进行方法功能增强
jQuery.fn.extend( {
	on: function( types, selector, data, fn ) {
        // 调用全局on方法
		return on( this, types, selector, data, fn );
	}
...
}
function on( elem, types, selector, data, fn, one ) {
   ...
   // elem参数指向jQuery对象,调用jQuery对象each方法
   return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}
jQuery.fn = jQuery.prototype = {
  ...
  each: function( callback ) {
        // each方法是由elem实例调用,因此this指向elem, 这里最终调用jQuery扩展的each方法,如下段代码
		return jQuery.each( this, callback );
	},
  ...
}
jQuery.extend( {
  ...
  each: function( obj, callback ) {
		var length, i = 0;
        // 判断是否是类数组,有length属性并且带有length - 1属性则为类数组,在init方法增强this对象时,已将        //this对象设置了这两个属性,因此增强后的this对象为类数组
		if ( isArrayLike( obj ) ) {
            
/**
                obj为jQuery对象,此对象是init方法返回的this,this为针对不同元素增强后的jQuery对象,下面代码是在init方法中对jQuery对象进行了属性增强,增加了长度属性以及0属性,
                if ( elem && elem.parentNode ) {

						// Inject the element directly into the jQuery object
						this.length = 1;
						this[ 0 ] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
            */
			length = obj.length;
			for ( ; i < length; i++ ) {
                // 方法回调,更改回调方法上下文环境为obj[i],实际上obj[i]就是init方法的this对象的0属性,                // this[ 0 ] = elem; elem对象就是html的dom元素                
                // elem = document.getElementById( match[ 2 ] ); 因此回调方法的上下文环境就为dom元素,                // 本例中就是a元素,即document.getElementById("hr_three")
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
            
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}
		return obj;
	},
  ...
});

最终调用回调方法

// this为dom元素,types为click, fn为触发方法
jQuery.event.add( this, types, fn, data, selector );

在jQuery.event.add方法中定义事件处理方法

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 !== "undefined" && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}
 ...
 // 生成处理对象
 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数组
 if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}
...

对elem增加click事件监听,elem为dom元素(本例中为id是hr_three的a元素)

if ( elem.addEventListener ) {
   // eventHandle是本方法中定义的事件处理方法,见上段代码
   elem.addEventListener( type, eventHandle );
}

至此,a元素已经绑定了click事件,当a元素产生点击事件时,会触发eventHandle指向的方法。

// eventHandle指向的方法参数e为pointerEvent对象
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 !== "undefined" && jQuery.event.triggered !== e.type ?
                    // elem为a元素,arguments为调用参数数组,元素0为参数e
                    // elem为jQuery.event.dispatch方法的上下文环境,arguments[0]为方法的唯一参数
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};

可以通过以下代码模拟元素增加监听效果,监听方法参数e就是pointerEvent对象:

var elem = document.getElementById("hr_three");
			elem.addEventListener( "click", function(e) {
                alert('阻止事件冒泡');
                e.stopPropagation();
				console.log(e);
			} );

继续jQuery.event.dispatch方法

jQuery.event = {
...
// event参数为pointerEvent对象
dispatch: function( event ) {
    // 将pointerEvent对象转换为jQuery.Event对象
    // Make a writable jQuery.Event from the native event object    
    event = jQuery.event.fix( event );
    // 取得处理对象数组
    handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
    args[ 0 ] = event;
    // 将处理对象数组放入处理队列属性中
    handlerQueue = jQuery.event.handlers.call( this, event, handlers );
    while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
        ...
        while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {
            ...
            /**
              调用处理方法,方法上下文为a元素,参数为jQuery.Event对象,处理方法event参数实际为args[0]指向的jQuery.Event对象,stopPropagation()方法为jQuery.Event实例方法,其中会将本实例的isPropagationStopped属性设置为true,因此再次外部while循环时,event.isPropagationStopped()即为true了。
              function(event){ 
				alert('阻止时间冒泡');
				event.stopPropagation(); 
			  }
            */
            ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
						handleObj.handler ).apply( matched.elem, args );
            
            ...
        }
        ...
    }
    
},
...
}

到此,一个完整的jQuery事件绑定及触发调用的整个过程就完成了。

相关推荐