jQuery源码分析之jQuery.event.special八问

本文深入解析了jQuery中各种事件处理机制,包括事件冒泡、特殊事件处理、事件代理及浏览器兼容性问题。重点介绍了如何处理不冒泡事件,如focus、blur等,并详细解释了jQuery如何实现这些事件的代理。
摘要由CSDN通过智能技术生成

参见博客:点击打开链接

首先观看该图

问题1:如果事件不冒泡怎么办?

 $("#n1").on("focus",function(e)
	{
	  console.log("n1 focus!");
	});
	$("#n3").on("focus",function(e)
	//子元素触发了focus,但是focus不冒泡,所以父元素n1不会触发!
	{
	  console.log("n3 focus!");
	});
	jQuery.event.trigger("focus",{name:"qinliang"},$("#n3")[0]);
这时候n1这个父元素的focus根本不会调用,因为focus本身就不会冒泡的!
            focus: {
			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && this.focus ) {
					try {
						this.focus();//让元素调用本地的函数function(){[native code]}!
						return false;
					} catch ( e ) {
						// Support: IE<9
						// If we error on focus to hidden element (#1486, #12518),
						// let .trigger() run the handlers
					}
				}
			},
			delegateType: "focusin"//代理的类型是focusin,focusin是冒泡的!
		}
那么jQuery.event.trigger是如何让他不触发父元素的focus方法的?

if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}
让这个不能冒泡的focus的trigger方法返回false就能够阻止元素往上面冒泡了,因为jQueyr.event.trigger已经直接返回了,不会获取target的父元素集合然后调用集合中每一个元素的focus方法!
 $("#n1").on("focusin",function(e)
	{
	  console.log("n1 focusin invoked!");
	});
	$("#n3").on("focus",function(e)
	//子元素触发了focus,但是focus不冒泡,所以父元素n1不会触发!
	//但是父元素如果绑定了focusin,那么还是会冒泡,这是JS机制
	//子元素获取到focus,表示focusin,但是focusin会冒泡,所以父元素focusin会执行!
	{
	  console.log("n3 focus!");
	});
	jQuery.event.trigger("focus",{name:"qinliang"},$("#n3")[0]);
focusin表示元素获取了焦点,这时候这个事件会往上冒泡,所以父元素focusin被调用来自与js机制,而不是jQuery的处理!
问题2:那么delegateType的作用是什么?

    $("#n1").on("focus","#n3",function(e)
	//其实他绑定的是focusin方法,因为他是代理,所以jQuery内部会把它设置为delegateType!
	{
	  console.log("n1 focusin invoked!");
	});
	jQuery.event.trigger("focus",{name:"qinliang"},$("#n3")[0]);
这时候n1是代理对象,所以他是被赋予focus的delegateType的,也就是focusin,这一点一定要注意,他是怎么做到的呢,我们看看jQuery.event.add方法

special = jQuery.event.special[ type ] || {};
      //如果含有selector表示当前DOM代理了满足selector的所有的同类的事件,那么获取delegateType!
     // If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;

这段代码表明,如果绑定事件的时候指定了selector,那么表示当前元素是代理对象,所以他绑定的事件是delegateType,此处也就是focusin,这是为什么n1的focus会执行,不过这个机制是因为jQuery内部的处理,而不是JS本身的机制!
问题3:blur方法是否也是上面的机制?

blur: {
	trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();//this表示DOM,第一个参数是data=[event,data]!
					return false;//因为失去焦点以后会自动触发父元素所有的focusOut,由JS机制来完成
				}
			},
			delegateType: "focusout"
		}
同样的道理,我们调用为元素注册blur时候,如果含有selector那么也会变成delegateType为focusout!
 $("#n1").on("blur","#n3",function(e)
	//其实他绑定的是focusin方法,因为他是代理,所以jQuery内部会把它设置为delegateType!
	{
	  console.log("n1 blur invoked!");
	});
	jQuery.event.trigger("blur",{name:"qinliang"},$("#n3")[0]);
这种机制来自于jQuery内部的处理,而不是JS本身的机制
问题3:click也是特殊事件,为什么?

  $("#n1").on("click",function(e)
	//click本身就会冒泡,所以直接会冒泡到n1上面!
	{
	  console.log("n1 blur invoked!");
	});
	jQuery.event.trigger("click",{name:"qinliang"},$("#n3")[0]);
click虽然会自己冒泡,但是对于checkbox调用trigger时候必须手动调用click,否则不会被选中;正是因为他会冒泡,所以可以直接返回,不用逐个获取他的父元素然后调用父元素的click事件;同时超链接的click事件不会执行默认行为!

               // For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
					this.click();//如果不手动调用click,那么checkbox不会被选中!
					return false;
					//return false表示不用获取他所有父元素的click事件,然后逐个执行了,因为他本身冒泡
					//通过JS冒泡机制就可以完成了!
				}
			},
			// For cross-browser consistency, don't fire native .click() on links
			//触发a标签的click不会打开超链接!
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
超链接的click不会打开默认的链接

	jQuery.event.trigger("click",{name:"qinliang"},$("#n3")[0]);
问题4:load事件为什么也是特殊事件?

解答:我们总不能让一个元素的load事件不断的往所有的父元素传递把,加载一个图片那么要load多少次啊,所以load事件不让冒泡

<div id="father">
     <img src="images/1.png" id="img"/>
 </div>
JS部分

 $("#father").on("load",function()//不会受到load事件,阻止冒泡
	{
	  console.log("log");
	});
	jQuery.event.trigger("load",{name:"qinliang"},$("img")[0]);
问题5:beforeunload在干嘛?

                beforeunload: {
			//dispatch中调用了,上下文是代理DOM,参数为事件对象
			postDispatch: function( event ) {
				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				//如果调用我们自己的函数同时有返回值,那么为JS事件对象的returnValue赋值,只是为IE有用啊!
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
问题6:mouseover,mouseenter等处理处理?

  $("#father").mouseenter(function()
	  {
	    console.log("father mouseenter");
	  });
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	  console.log(data);//这时候你会发现,events域里面绑定的是mouseover域而不是mouseenter!
所以jQuery把mouseenter,mouseleave等不冒泡的事件全部替换为mouseout等会冒泡的事件

  $(document).on("mouseenter",function()
	   {
	     console.log("document!");
	   });
      $("#father").on("mouseenter","#child",function(arg)
	  {
        //mouseout等调用函数,上下文是target对象,第一个是event对象该对象有
		//关于该事件的所有的信息!
		console.log(arg);
	  });
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	  console.log(data);//这时候你会发现,events域里面绑定的是mouseover域而不是mouseenter!
上下文是currentTarget对象,第一个参数是event对象,该对象含有本事件的所有的信息!同时,因为一开始绑定的事件就把不冒泡的绑定为冒泡的事件, 所以代理事件肯定会被调用!
// Create mouseenter/leave events using mouseover/out and event-time checks
jQuery.each({
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		//因为他们不冒泡,所以对他们要做同样的处理来代理
		delegateType: fix,
		bindType: fix,
		//bindType也是mouseover等会冒泡的事件,所以通过on方法添加mouseenter事件其实添加的是mouseover!
		handle: function( event ) {
			var ret,
				target = this,//注意:即使是通过事件代理了,那么这时候this一直是目标对象而不是代理对象!
				related = event.relatedTarget,//就是relatedTarget!
				handleObj = event.handleObj;
			// For mousenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			//目标进入浏览器窗口的时候没有relatedTarget!
			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
				event.type = handleObj.origType;
				//调用我们自己的回调函数,上下文是目标对象!
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
});
问题7:checkbox和radio在IE中不冒泡,那么如何处理,如何针对IE解决?

 <div id="father">
        男:<input type="checkbox" value="男" name="qin"/>
	    女:<input type="checkbox" value="女" name="qin"/>
 </div>
father元素无法捕获到子元素的change事件

 document.getElementById("father").οnchange=function()
	 {
	   alert("change!");//IE中radio和checkbox是不冒泡的!
}
在jQuery.event.add方法中,如果没有setup才会按照JS常用的事件绑定来完成addEventListener或者attachEvent

// Only use addEventListener/attachEvent if the special events handler returns false
		if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {//IE的checkbox等不冒泡返回了false
			// Bind the global event handler to the element
			if ( elem.addEventListener ) {
				elem.addEventListener( type, eventHandle, false );
			} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
			}
	}
我们可以看出来,在setup中上下文是绑定事件的DOM,也就是代理对象,第一个参数是传入的data,第二个参数是命名空间,第三个参数是通用回调函数( 注意:这里IE的checkbox不冒泡返回了false,所以还是通过通用的函数绑定的事件,但是他依然有下面的自定义事件

我们可以看到,针对IE的处理是添加自定义的事件;

if (!support.changeBubbles ) {//针对IE的checkox个radio不冒泡的情况
	jQuery.event.special.change = {
		//我们用自定义事件来处理这种在IE中不冒泡的行为!
		setup: function() {
			//var rformElems = /^(?:input|select|textarea)$/i,
			if ( rformElems.test( this.nodeName ) ) {
				// IE doesn't fire change on a check/radio until blur; trigger it on click
				// after a propertychange. Eat the blur-change in special.change.handle.
				// This still fires onchange a second time for check/radio after blur.
				if ( this.type === "checkbox" || this.type === "radio" ) {
					//添加自定义事件propertychange._change,放入数据库是propertychange事件
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {//
							this._just_changed = true;
						}
					});
					//添加自定义事件click._change,添加进去为click!
					jQuery.event.add( this, "click._change", function( event ) {
						if ( this._just_changed && !event.isTrigger ) {
							this._just_changed = false;
						}
						// Allow triggered, simulated change events (#11500)
						jQuery.event.simulate( "change", this, event, true );
					});
				}
				//这里已经返回了,后面的事件不会被添加!
				return false;
			}
			// Delegated event; lazy-add a change handler on descendant inputs
			//添加自定义事件beforeactivate._change
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
				var elem = e.target;
				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
					jQuery.event.add( elem, "change._change", function( event ) {
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event, true );
						}
					});
					jQuery._data( elem, "changeBubbles", true );
				}
			});
		},
        //通用回调函数!
		handle: function( event ) {
			var elem = event.target;//我们绑定了自定义事件,所以调用handle就是调用我们自定义的函数!
			// Swallow native change events from checkbox/radio, we already triggered them above
			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
				return event.handleObj.handler.apply( this, arguments );
			}
		},
        //移除事件的函数,所以._change的事件,通过正则表达式完成!
		teardown: function() {
			jQuery.event.remove( this, "._change" );
			return !rformElems.test( this.nodeName );
		}
	};
}
通过 该图你会发现,对于IE来说,是添加了两个自定义事件,分别为propertychange和click。加上我们自己添加的change事件,所以元素内部保存了三个事件! 如果不是checkbox和radio,当不支持冒泡的时候,我们只会添加一个事件,即beforeactivate事件!
我们看看这个事件在jQuery.event.trigger中是如何被调用的

         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" );//trigger时候获取类型然后调用
			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();
				}
			}
		}
因为这种自定义的事件或者特殊事件都是从target开始逐层往上开始调用该事件的,逐层调用父级元素的type和ontype事件,父元素集合如[input#man, div#father, body, html, document, Window], 所以同类的事件全部被调用,所以实现了冒泡。
问题8:focusinBubble可以解决,默认情况下会添加jQuery.event.special["focusin/focusout"]?

// Create "bubbling" focus and blur events
if ( !support.focusinBubbles ) {
	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};

		jQuery.event.special[ fix ] = {
			setup: function() {
				//默认在document对象上面添加focusin/focusout,如果调用那么添加事件focusin!
				var doc = this.ownerDocument || this,
					attaches = jQuery._data( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = jQuery._data( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
		};
	});
}

问题9:针对IE修改submit代理事件?

//针对IE修复submit的代理事件
// IE submit delegation
if ( !support.submitBubbles ) {
	jQuery.event.special.submit = {
		setup: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}
			// Lazy-add a submit handler when a descendant form may potentially be submitted
			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
				// Node name check avoids a VML-related crash in IE (#9807)
				var elem = e.target,
					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
					jQuery.event.add( form, "submit._submit", function( event ) {
						event._submit_bubble = true;
					});
					jQuery._data( form, "submitBubbles", true );
				}
			});
			// return undefined since we don't need an event listener
		},
		postDispatch: function( event ) {
			// If form was submitted by the user, bubble the event up the tree
			if ( event._submit_bubble ) {
				delete event._submit_bubble;
				if ( this.parentNode && !event.isTrigger ) {
					jQuery.event.simulate( "submit", this.parentNode, event, true );
				}
			}
		},
		teardown: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
			jQuery.event.remove( this, "._submit" );
		}
	};
}


总结:

(1)document.activeElement可以获取到当前获取到焦点的元素,如果没有选中,activeElement就是body,但是不是所有的浏览器都支持!

(2)弄懂即使是focus,blur,submit,change,mouseenter,mouseleave,pointerenter,pointerleave也能被代理!

(3)因为focus和blur最大的问题在于他们不冒泡,因此IE的focusin和focusout被DOM3级事件采纳为标准。于是这里的focus用focusin进行代理,blur用focusout代理!Opera用的是DOMFocusIn和DOMFocusOUT!

(4)对于鼠标事件来说,除了mouseenter,mouseleave外都是冒泡的,所以在jQUery源码中用mouseover,mouseout来作为delegateType,进而当用delegate进行绑定时候也能进行事件代理! 注意:mouseout是移入另外一个元素的时候就会触发,但是另一个元素可以是该元素的子元素或者外部元素!同时mouseover也是移入元素时候触发,移入的元素可以是元素的子元素(注意空格部分也是子元素,该部分的nodeType是3);mouseout见点击打开链接 mouseover见点击打开链接

(5)pointerenter,pointerleave相关知识点击打开链接

(6)IE的submit事件也能够代理,但是要注意一点,通过表单的submit()方法来提交表单不会触发submit事件,因此在调用该方法之前要进行数据的验证!

(7)IE的change事件进行代理。change事件对不同的表单控件触发的次数是不同的,对于input和textarea当他们从获得焦点到失去焦点且value值改变的时候会触发change,对于select只要用户选择了不同的选项就会触发change,换句话说不失去焦点也触发了change事件!

(8)对于这些方法,只要在调用函数return false之后那么不仅会阻止默认行为,同时也会阻止冒泡,我们把dispatch中的一段源码附上:

 ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )  
                            .apply( matched.elem, args );  
        //打印undefined,必须事件类型有相应的handle才行!否则返回undefined!  
               // alert(ret);  
                //打印undefined,result属性不存在!  
                //alert(event.result);  
                    if ( ret !== undefined ) {  
                        if ( (event.result = ret) === false ) {  
                            event.preventDefault();  
                            event.stopPropagation();  
                        }  
                    }  
//下面给出一段测试代码,这段代码不管怎么点,相应的checkbox都不会被选择上:
function handler(event){
return false;
}
$("input").click(handler);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值