jQuery源码分析之jQuery.event.trigger及jQuery.Event十问

徐焱
2023-12-01

问题1:jQuery.event.trigger常见调用方式有那些?

实例trigger方法

trigger: function( type, data ) {
		return this.each(function() {
			jQuery.event.trigger( type, data, this );//调用了jQuery.event.trigger方法
		});
	}
实例triggerHandle方法

triggerHandler: function( type, data ) {
		var elem = this[0];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );//jQuery.event.trigger调用
		}
	}
ajax方法中的调用方式

        jQuery.event.trigger("ajaxStart");//触发事件
问题2:trigger一开始就判断传入的type参数是否有type属性,那么我们就先来看一看jQuery.Event对象?

jQuery.Event = function( src, props ) {
	// Allow instantiation without the 'new' keyword
	if ( !(this instanceof jQuery.Event) ) {//作用域安全的构造函数,所以即使没有new jQuery.Event也是相当于用new来构造对象的!
		return new jQuery.Event( src, props );
	}
	// Event object
	if ( src && src.type ) {//可以传入一个Event对象,也可以只是传入一个字符串如new jQuery.Event("click");
		this.originalEvent = src;
		this.type = src.type;
		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&
				// Support: IE < 9, Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;
	// Event type
	} else {
		this.type = src;
	}
	// Put explicitly provided properties onto the event object
	if ( props ) {//第二个参数会被直接封装到this对象上面
		jQuery.extend( this, props );
	}
	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();
	// Mark it as fixed
	this[ jQuery.expando ] = true;//事件对象也有钥匙
};
分开来看一看

if ( src && src.type ) {//传入event对象
		this.originalEvent = src;
		this.type = src.type;
		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&
				// Support: IE < 9, Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;
	// Event type
	} else {//传入字符串
		this.type = src;
	}
表示我们可以传入一个event对象,这时候该event对象会用originalEvent属性保存;也可以传入一个字符串,该字符串表示事件的类型。
if ( props ) {
		jQuery.extend( this, props );
	}
传入的第二个参数会直接封装到jQuery.Event对象上面,作为事件的多余的参数!
	this.timeStamp = src && src.timeStamp || jQuery.now();
	// Mark it as fixed
	this[ jQuery.expando ] = true;
构建的event对象有仓库和timeStamp属性!
我们再来看看jQuery.Event的原型对象

正常情况下不阻止默认行为,不阻止冒泡

isDefaultPrevented: returnFalse,//默认行为不阻止
isPropagationStopped: returnFalse,//不阻止冒泡
isImmediatePropagationStopped: returnFalse,//不阻止冒泡
preventDefault调用的时候其实还是调用js对象的event,而不是jQuery.Event对象的方法, 所以需要从jQuery.Event对象中剥离出来js的event对象,也就是通过originalEvent

preventDefault: function() {
		var e = this.originalEvent;
		this.isDefaultPrevented = returnTrue;
		//调用了preventDefaule就会把修改isDefaultPrevented为true
		if ( !e ) {
			return;
		}
		// If preventDefault exists, run it on the original event
		if ( e.preventDefault ) {
			e.preventDefault();
		// Support: IE
		// Otherwise set the returnValue property of the original event to false
		} else {
			e.returnValue = false;
		}
	}
stopPropagation还是通过原生js对象的来进行,所以也要通过jQuery的event对象获取到js对象的event对象

stopPropagation: function() {
		var e = this.originalEvent;
		this.isPropagationStopped = returnTrue;//修改为已经阻止冒泡了
		if ( !e ) {
			return;
		}
		// If stopPropagation exists, run it on the original event
		if ( e.stopPropagation ) {
			e.stopPropagation();
		}
		// Support: IE
		// Set the cancelBubble property of the original event to true
		e.cancelBubble = true;
	}
stopImmediatePropagation也是通过原生JS对象来阻止冒泡的

stopImmediatePropagation: function() {
		var e = this.originalEvent;
		this.isImmediatePropagationStopped = returnTrue;//阻止冒泡行为
		if ( e && e.stopImmediatePropagation ) {
			e.stopImmediatePropagation();
		}
		this.stopPropagation();
	}
我们来看一看stopPropagation和stopImmediatePropagation的区别:

 $("body").on("click",function(e)
	  {
		console.log("body");
	  });
    $("#parent").on("click",function(e)
	{
	   console.log("click1");
	});
   $("#parent").on("click",function(e)
	{
	   console.log("click2");
	});
默认情况下点击parent会调用所有的回调函数!
我们调用一下stopPropagation:

 $("body").on("click",function(e)
	  {
		console.log("body");
	  });
    $("#parent").on("click",function(e)
	{
	   console.log("click1");
	   e.stopPropagation();//防止冒泡,但是两次click还是都会调用
	});
   $("#parent").on("click",function(e)
	{
	   console.log("click2");
	});
这时候会调用parent元素的两次click事件,但是body不会捕获到事件,因为已经阻止冒泡了。

 $("body").on("click",function(e)
	  {
		console.log("body");
	  });
    $("#parent").on("click",function(e)
	{
		 console.log("click1");
		 e.stopImmediatePropagation();//防止冒泡,同时后面所有的click事件都不会被调用!
	});
   $("#parent").on("click",function(e)
	{
	   console.log("click2");
	});
这时 候只会调用parent元素的一次click事件,和stopPropagation一样,body也不会捕获到事件!注意:前面绑定的事件会先调用,所以他调用任何一个方法都会阻止冒泡,但是stopImmediatePropataion会取消甚至后面 本元素的click事件!

问题3:jQuery.event.trigger步骤是怎么样的?

第一步:看event对象是否有type属性和namespace属性,因为如果有namespace那么只能触发相同namespace属性的事件

	eventPath = [ elem || document ],
			//如果传入的event有type属性,那么获取该type属性
			type = hasOwn.call( event, "type" ) ? event.type : event,
			//如果有namespaces就获取namespaces属性!
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
第二步:文本注释节点直接返回,同时focus和blur采用了代理,所以不直接调用

if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}
		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		//	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}
第三步:根据用户传入的事件类型(注意,该事件类型可能含有命名空间)构建自己的命名空间和jQuery.Event对象,以便触发事件

  //如果type含有点好,那么第一项就是事件类型,如click,其余为命名空间!
		if ( type.indexOf(".") >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}
		//如果type没有冒号,那么ontype就是在前面加上on
		ontype = type.indexOf(":") < 0 && "on" + type;
		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		//如果是jQuery.Event对象,那么是有仓库的,所以直接返回。如果不是jQuery.Event对象
		//那么我们重新包装他为jQuery.Event对象,同时使得该对象具有原始event对象的所有信息!
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );
		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		//为event设置namespace属性,其中namespace属性就是用户自己传入的namespace属性,
		//namespace_re属性是一个正则表达式,这个正则表达式通过用户自己传入的namespace来构建!
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;
		// Clean up the event in case it is being reused
		//把event的result属性设置为undeined,以便回收使用!
		event.result = undefined;
		//设置target对象,如果没有target,那么表示target就是传入的参数,记住:这里是trigger不是用户行为触发的!
		if ( !event.target ) {
			event.target = elem;
		}
第四步:如果用户调用trigger传入了数据,那么结果数据就是[event,data]类型。同时,如果是特殊类型,同时调用特殊类型的trigger结果是false,那么什么也不做

	// Clone any incoming data and prepend the event, creating the handler arg list
		//如果没有传入data,那么data就是event对象,makeArray方法如果传入第二个参数表示把第一个data内容放入到event中
		//所以如果传入了data那么data就是[event,data]!
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );
		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		//调用special自己有的trigger方法!
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}
第五步:获取到当前DOM应该冒泡的路径,保存到eventPath数组中,而且采用的是push方法, 所以像document等元素是在数组最后的!

	// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
			bubbleType = special.delegateType || type;
			//	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/
			//也就是说bubbleType和type链接起来不是这种类型,那么获取父元素.因为focus和blur不冒泡!
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				//	eventPath = [ elem || document ]默认是document
				//eventPath里面放入的是当前对象的所有父元素
				eventPath.push( cur );
				tmp = cur;
			}
             //给eventPath里面添加window的defaultView或者parentWindow
			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}
第六步:第五步获取到了DOM应该冒泡的路径保存到了eventPath数组中,这里就对这个eventPath中的数组进行调用

	i = 0;
		//获取传入的对象elem的每一个父元素,同时这个event没有停止冒泡!
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
           //如果event.type大于1那么就是bubbleType否则是bindType或者type本身
			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;
			// jQuery handler
			//不断获取当前元素和父元素的events对象,然后获取该对象的type类型对象,同时获取该对象的handle就是要执行的函数对象
			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			//如果回调函数存在,那么调用该函数,传入的数据是data,也就是传入trigger函数传入的参数对象
			if ( handle ) {
				handle.apply( cur, data );
			}
            //本地handler对象,获取该对象的on事件
			// Native handler
			handle = ontype && cur[ ontype ];
			//如果on事件存在,同时该对象能够保存数据也就是Element对象
			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
				//为event设置result值为handle调用后的结果
				event.result = handle.apply( cur, data );
				//如果返回是false,那么就直接停止冒泡
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}

JS实例:

$("#n1").on("click",function(){console.log("n1 ")});  
	$("#n2").on("click",function(){console.log("n2 ")});  
	$("#n3").on("click",function(){console.log("n3 ")});  
      console.log($("#n3")[0].ownerDocument.defaultView);//Chrome是defaultView获取window对象,其它浏览器用parentWindow获取
	window.οnclick=function(e)
	{
	  console.log("window");
	}

HTML部分

<div id="n1">  
    <div id="n2">  
         <input type="checkbox" id="n3" href="www.baidu.com" style="display:inline-block;height:100px;width:100px;border:1px solid red;"> 
    </div>  
</div>  
这时候点击checkbox你会发现,最先调用的n3,然后是n2,最后是n1和window,这就是上面为什么会先保存target,然后才保存父元素的原因,这也是事件冒泡时候应该有的顺序; 至于为什么也会触发window对象的事件,是因为在eventPath中保存了elem.ownnerDocument.defaultView或者parentWindow来保存window对象!

总之:调用的时候是获取eventPath中每一个DOM的特定类型的事件集合,同时调用该DOM的通用回调函数, 通用回调函数上下文为该DOM,该函数传入的唯一的参数是上面第四步构建的data对象!同时,如果调用的该类型事件还有ontype事件,那么ontype事件也需要调用, 调用函数上下文是当前DOM,参数是第四步构建的数据对象,但是如果ontype返回了false,那么就不会向父元素冒泡了!总之,按照jQuery.event.trigger来触发某一类特殊的事件的话, 他会触发该DOM所有父元素的同类的事件,包括on类型事件和通过其它方式绑定的事件!
最后一步:如果调用trigger方法,那么我需要触发对象的默认行为,这是为了和用户触发事件行为保持一致必须做的,如点击checkbox时候必须选中等

	//设置type的值
		event.type = type;
		// If nobody prevented the default action, do it now
		//如果不是一次调用,同时也没有阻止默认行为,那么我们还需要触发默认行为,这一点很重要!
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {
				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
                     //获取ontype事件句柄,从而使得在已经执行了type方法后不执行ontype事件!
					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];
                    //将ontype句柄设置为null
					if ( tmp ) {
						elem[ ontype ] = null;
					}
					// Prevent re-triggering of the same event, since we already bubbled it above
					//防止再次执行同样的事件,因为已经冒泡了!
					jQuery.event.triggered = type;
					try {
						//执行type函数!
						elem[ type ]();
					} catch ( e ) {
						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					//将trigger设置为null
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}
问题4:我们这里采用了jQuery.event.trigger来调用函数的,那么我们回调函数中的上下文和参数是什么?

jQuery.event.trigger调用

	  trigger: function( event, data, elem, onlyHandlers ) //trigger第二个参数是数据,不过我们会把data最终转化为[event,data]类型
 通过trigger触发事件的时候参数和上下文通过下面的代码来触发:

	 event.result = handle.apply( cur, data );//这里的data格式为[event,data]类型,cur是事件流当前所在的元素!
例子:HTML部分如上

   $("#n1").on("click",function(e,args){console.log(e);console.log(args);});//this就是当前DOM!
  jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);//这时候你就知道,其实通过trigger调用的时候第一个参数是jQuery.Event对象,args是参数!
问题5:上面最后一步还是没懂,什么叫阻止元素的默认行为?

还是上面的HTML结构

$("#n1").on("click",function(e,args){console.log(e);});  
  $("#n1")[0].οnclick=function(e)//通过这种方式添加事件是JS原生添加事件的方式,不是通过添加到DOM的events域上面的,通过elem[ontype]就可以访问的!
	{
	  console.log("on click");
	}
jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
注意:通过onclick直接添加和通过on或者bind添加有本质的区别,后者是添加到DOM的events域下面,但是前者直接通过elem[ontype]就能够访问了!所以,最后一步就是针对这种添加事件的情况的,tmp = elem[ ontype ]就是获取ontype事件类型,这种类型直接添加到DOM的onclick属性下,所以后面要执行元素的默认行为!
如何阻止该事件的默认行为?

$("#n1").on("click",function(e,args){e.preventDefault();});//阻止默认行为
    $("#n1")[0].οnclick=function(e)//该例子不管你怎么点击,checkbox都不会被选中!
	{
	  console.log("on click");
	}
 jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
因为,通过源码我们可以看出:这种默认行为是最后才被执行的,所以如果前面通过on或者bind等方式绑定的事件调用了preventDefault,那么默认行为就不会被执行了。
问题6:trigger调用能够阻止默认行为,那么trigger阻止了默认行为之后,用户手动触发事件是否能够再次触发事件?

解答:可以

$("#n1").on("click",function(e,args){e.preventDefault()});  
    $("#n1")[0].οnclick=function(e)
	{
	  console.log("on click");
	}
	jQuery.event.trigger("click",{name:"qinliang"},$("#n1")[0]);
这种方式,n1上面的click和onclick都会触发, 因为上面已经说了:一步一步往上执行,每一步都会执行on绑定的和ontype绑定的所有的事件,但是只要有一个调用了preventDefault那么最后一步压根不会执行,所以默认行为就不存在了!所以,这一步调用trigger时候两者都会触发,但是当我们以后点击的时候两者还是会触发,只是默认行为不会触发了!
问题7:jQuery.event.trigger是如何阻止默认行为的?

               if ( !onlyHandlers && !event.isDefaultPrevented() ) {
			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {
				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
                     //获取ontype事件句柄,从而使得在已经执行了type方法后不执行ontype事件!
					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];
                    //将ontype句柄设置为null
					if ( tmp ) {
						elem[ ontype ] = null;
					}
					// Prevent re-triggering of the same event, since we already bubbled it above
					//防止再次执行同样的事件,因为已经冒泡了!
					jQuery.event.triggered = type;
					try {
						//执行type函数!
						elem[ type ]();
					} catch ( e ) {
						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					//将trigger设置为null
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
如果用户没有调用preventDefault了,那么我们就会调用elem[type],那么elem[type]这个函数是什么呢,请看 该图。这也就是说,这个默认行为是在最后一步被执行的。 开始的时候是从target一直到父元素逐层调用他们通过on方法或者ontype方法添加的回调函数。最后一步才开始调用默认的函数!

问题7:jQuery.event.triggered = type;在干嘛?
                // Prevent re-triggering of the same event, since we already bubbled it above
					//防止再次执行同样的事件,因为已经冒泡了!
					jQuery.event.triggered = type;
					try {
						//执行type函数!
						elem[ type ]();
					} catch ( e ) {
						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					//将trigger设置为null
					jQuery.event.triggered = undefined;
解答:在默认行为函数执行之前其值为type,默认行为执行过了,其值为undefined!

问题8:如果有些事件如blur,focus无法冒泡怎么办,这时候父元素应该触发什么类型事件?

$("#n1").on("focusin",function(e){console.log("n1 on focus");});
	$("#n3").on("focus",function(e){console.log("on focus");});
	jQuery.event.trigger("focus",{name:"qinliang"},$("#n3")[0]);
这时候你就会明白n3冒泡的应该是focusin事件,如果把绑定给n1的focusin修改为focus,那么n1上面的事件不会执行!参见 博客

问题9:如何调用自定义事件?

$("#n3").on("qinliang",function(e)
	{
	  console.log("qinliang invoked!");
	});
	//自定义事件的执行,on方法把事件添加到events域下面,同时trigger把events域下面的同名事件
	//拿出来执行!
	jQuery.event.trigger("qinliang",{name:"qinliang"},$("#n3")[0]);
问题10:jQuery.event.trigger源码?

trigger: function( event, data, elem, onlyHandlers ) {
		var handle, ontype, cur,
			bubbleType, special, tmp, i,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
		cur = tmp = elem = elem || document;
		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}
		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}
		if ( type.indexOf(".") >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf(":") < 0 && "on" + type;
		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;
		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}
		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );
		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}
		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}
		// Fire handlers on the event path
		i = 0;
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;
			// jQuery handler
			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;
		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];
					if ( tmp ) {
						elem[ ontype ] = null;
					}
					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					try {
						console.log(elem[type]);
						elem[ type ]();
					} catch ( e ) {
						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	}

注意:最后的elem[type]如果你去alert出来会发现结果是function click(){[native code]}也就是说这里是为target对象调用默认方法,如果是checkbox就是表示选中,如果是超链接默认行为就是表示打开等!但是对于click方法来说,根据源码,如果你是triggerClick,那么根本不会打开超链接,其它元素默认事件会触发,唯独超链接的click不会!
上面你必须要弄清楚:

function A( event,data ){
    alert( 'A' +data.name);
}
function B( event,data ){
    alert( 'B' +data.name);
}
function C( event,data ){
    alert( 'C' +data.name);
}
var $btn1 = $("#btn1");
// 为btn1元素的click事件绑定事件处理函数
$btn1.bind( "click.foo.bar", A );
$btn1.bind( "click.test.foo", B );
$btn1.bind( "click.test", C );
// 触发btn1的包含命名空间test的click事件
$btn1.trigger("click.foo",{name:"xxx"},$btn1); // 触发B、C
(1)为什么trigger一次会同时弹出B和C呢?对同一个对象来说,bind方法调用了3次,那么在events里面放入了3个handleObj,但是每次都是对应的同一个 handle,也就是这个元素的handle。当你真正调用trigger的时候,其实就是拿着这个函数去执行!这个函数内部调用了dispatch方法,dispatch方法会通过handlers方法判定出需要执行多次,因为handleObj有多个对象都能够满足执行要求!所以说 通过点击执行和通过trigger执行是一样的,都是调用handle函数!

(2)那么当调用trigger的时候如何保证相应的namespace下的所有函数都会执行呢?这时候trigger中的namespace_re代码就起作用了,他会根据自己的命名空间创建相应的正则表达式,然后保存到event对象中!同时event会传递到handle方法中,然后传递到dispatch中,该方法中有一个判断 if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) )这样就能够保证通过on方法添加的handleObj如果满足这个正则表达式就能够执行!如第一个handleObj是"foo.bar",第二个是"test.foo",第三个是"test",于是用trigger的正则表达式去匹配,最后就会执行B和C!

(3)我们得出结论,通过点击触发事件和通过trigger触发事件本质上没有任何区别(除了click作用于a),都是调用相应元素的handle方法!该冒泡的时候也是会冒泡的!回调的时候会给相应的绑定的参数传递event对象和data对象!

(4)为什么我说上面trigger方法该冒泡的时候也会冒泡呢?如果子元素调用trigger("click.test")那么所有的父元素的"click"为什么不会执行呢?

$(document.getElementById("container")).on("click",function(){alert("invoked container!")})
$(document.getElementById("content")).on("click",function(event){alert("invoked content!"+event.type)})
//拿着上面所有的click事件类型
$(document.getElementById("content")).trigger("click.test");
我们上面说过通过trigger和手动调用的结果是一样的,那么逻辑肯定也是一样的!当trigger("click.test")的时候,那么就会拿着content目标元素和父元素的所有的引用保存到数组中间,然后不断进行冒泡,当在target阶段的时候发现了自己的click事件,该不该触发呢?我们上面说过在触发事件的时候会用namespace_re保存触发时候的正则表达式,这里是"test",然后拿着这个正则表达式和所有的目标对象和父元素对象的正则表达式进行对比,也就是用trigger时候的正则表达式判断绑定事件时候的正则表达式,最后就是/test/.test("")都是false结果就是目标对象和父元素的对象的所有的同类事件都不会执行!但是如果把on中的字符串修改为"click.test"那么就都会触发!(注意: 正则表达式仔细分析你会发现,如果绑定事件用了"click.foo.test"触发时候用"click.test.foo"也会触发!因为在 jQuery.event.add里面和jQuery.event.trigger里面都对命名空间数组进行了sort了!)

如果叫你设计一个触发一个对象的事件,你会怎么设计?

(1)获取该元素以及该元素所有的父元素,至于为什么用父元素是为了用于冒泡的!得到一个DOM集合!

(2)循环遍历DOM集合,获取每一个元素上面相应的事件,如果i<1获取绑定事件,否则获取冒泡事件!并且调用事件!

(3)循环结束以后,对target对象调用原生方法,使得他能够完成浏览器默认的动作,如checkbox就是选中,a就是打开链接!

 类似资料: