jQuery源码分析之tearDown和setup

问题1:如果我绑定focusin,那么底层是如何处理的?

在jQuery.event.add方法中

	special = jQuery.event.special[ type ] || {};//获取初始事件的对象
	// If selector defined, determine special event api type, otherwise given type
	type = ( selector ? special.delegateType : special.bindType ) || type;//如果当前DOM是代理元素,也就是调用时候含有selector
	// Update special based on newly reset type
	special = jQuery.event.special[ type ] || {};//那么获取代理类型的special对象,用于内部处理!如代理类型有可能可以冒泡!
获取这种特殊类型对应的对象,如果当前DOM是代理元素那么获取代理,那么获取代理类型的特殊的对象!
如果是特殊类型,同时这种类型含有setup方法,同时该方法没有返回false,那么就用setup方法绑定事件

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {//返回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中如何绑定事件的

jQuery.event.special[ fix ] = {
			setup: function() {
			//默认在document对象上面添加focusin/focusout,如果调用那么添加事件focusin!
			var doc = this.ownerDocument || this,
			attaches = jQuery._data( doc, fix );//看看该DOM(默认是document)上面有没有绑定focusin和focusout事件!
				if ( !attaches ) {
				//console.log(orig);其中orig是focus,第三个参数表示捕获阶段而不是冒泡阶段
			doc.addEventListener( orig, handler, true );//在document的捕获阶段添加focus/blur事件
				}
				//为document对象添加了内部数据了,其中参数名是focusin/focusout!
				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
			}
我们把focus事件绑定在document对象上面,而且是处于该对象的捕获阶段;更厉害的是我们为该document绑定了一个内部数据,数据格式是对象{focusin:1},所有元素绑定的focusin方法实际上是在document对象上面不断添加后面的数字。见下例:

   $("#father").on("focusin",function(){
	     //特殊事件
	  });
	  var expando=jQuery.expando;
	  var key=document[expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	  console.log(data);//这时候你会发现,events域里面绑定的是mouseover域而不是mouseenter!
这时候是{focusin:1},见 该图,然而,我们把js代码改成下面这种格式,那么这时候document上面绑定的数据就是{focusin:2}!

 $("#child").on("focusin",function(){
	     //特殊事件
	  });
      $("#father").on("focusin",function(){
	     //特殊事件
	  });
	  var expando=jQuery.expando;
	  var key=document[expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	  console.log(data);//这时候你会发现,events域里面绑定的是mouseover域而不是mouseenter!
通过 该图你会发现,这时候数据类型变成了{focusin:2}!
问题2:上面都是在document对象上面添加focus/blur事件,而且是在捕获阶段添加的,因此只要任何元素获取到焦点那么就会调用该方法,那么移除事件的tearDown方法如何?

	          //tearDown就是从document对象上面把元素删除!
			teardown: function() {
				var doc = this.ownerDocument || this,
					//上面添加的时候focusin是1,现在是0了!于是直接从document上面把
				    //把他的内部数据移除,同时把document对象上面绑定的捕获阶段的事件处理函数移除!
					//真狠:移除事件+移除document内部数据!
				attaches = jQuery._data( doc, fix ) - 1;
				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
通过这里的分析,你应该明白,每次调用tearDown实际上会把上面的focusin后面的数字减一,如果减为0了,那么就会移除document对象上面的事件处理函数,同时把document内部的数据全部移除,也就是focusin也不存在了。 所以,如果给元素绑定了focusin或者focusout事件,那么实际上是给document添加了一个blur/focus事件,所以只要元素获取到焦点就会回调他们自定义的handle函数,该函数里面会继续调用jQuery.event.trigger,从target一直到window逐层进行调用focus/blur,从而实现focus/blur也冒泡!

问题3:上面会不断的移除事件,移除事件必须回调函数相同,那么回调函数在那里?

         var handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );//fix为focusin/focusout!
			};
在我们分析simulate之前我们看看他的调用方式

	jQuery.event.simulate( "change", this, event, true )

jQuery.event.simulate源码如下

	simulate: function( type, elem, event, bubble ) {
		// Piggyback on a donor event to simulate a different one.
		// Fake originalEvent to avoid donor's stopPropagation, but if the
		// simulated event prevents default then we do the same on the donor.
		var e = jQuery.extend(
			new jQuery.Event(),
			//jQuery.Event对象具有JS的event的所有信息,同时也具有type,isSimulated等属性!
			event,
			{
				type: type,
				isSimulated: true,
				originalEvent: {}
			}
		);
		//如果冒泡,那么继续调用jQuery.event.trigger
		//否则调用jQuery.event.dispatch方法!
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );
		} else {
			jQuery.event.dispatch.call( elem, e );
		}
		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

也就说,这个方法里面如果规定了必须冒泡,那么调用jQuery.event.trigger,否则直接调用jQuery.event.dispatch方法!那么两者有什么区别的,我现在觉得前者是实现冒泡的,也就是说,他会实现从当前元素target要window元素的逐层冒泡,逐层检查每一层的元素是否有我们需要的事件,如果有,那么就执行。而后者的目地就是实现分析从delegateTarget下的所有的元素具有的同名事件的selector是否是我代理的,如果是我代理的,那么就会执行!

 $("html").on("click",function(){  
		 console.log("html focus in");  
	  }); 
	$("body").on("click",function(){  
		 console.log("body focus in");  
	  }); 
	$("#father").on("click",function(){  
		 console.log("father focus in");  
	  });  
	jQuery.event.trigger(new jQuery.Event("click"),{},$("#child")[0]);
这里,三个函数都会执行,所以trigger的作用可以模拟冒泡,这也是focus/blur用jQuery.event.trigger来模拟冒泡的原因!

 $("html").on("click","#child",function(){  
		 console.log("html focus in");  
	  }); 
	  //因为下面dispatch指定了只会到body,所以html中的click不会执行!
	  $("body").on("click","#child",function(){  
		 console.log("html focus in");  
	  }); 
	$("body").on("click","#father",function(){  
		 console.log("body focus in");  
	  }); 
	var expando=jQuery.expando;  
    var key=$("body")[0][expando];//上面的focusin表示自己为document对象添加了内部数据focusin!  
    var walhouse=jQuery.cache;  
    var data=walhouse[key];  
    console.log(data);
	var e=new jQuery.Event("click");
	e.target=$("#child")[0];
	//必须指定target,表示从那个元素向上一直到delegateTarget也就是body!
	//$("body")[0]表示上下文是body元素,也就是最高级别的父元素是body!
   jQuery.event.dispatch.call($("body")[0],e);  
这里只有body下面的两个click事件会被执行,因为dispatch的上下文是body,那么调用他只会遍历body下面的子元素的click事件对应的handleObj数组,而html不再body下面,所以他的回调不会执行!所以,用jQuery.event.dispatch丝毫没有冒泡的意思。这就是jQuery.event.simulate的作用,如果冒泡就调用jQuery.event.trigger否则调用jQuery.event.dispatch!

通过这个例子,你就会发现,本来不冒泡的focus/blur事件却可以被代理了

 $("html").on("focus","#child",function(){  
		 console.log("html focus in");  
	  }); 
	  //因为下面dispatch指定了只会到body,所以html中的click不会执行!
	  $("body").on("focus","#child",function(){  
		 console.log("html focus in1");  
	  }); 
	$("body").on("focus","#father",function(){  
		 console.log("body focus in2");  
	  }); 

问题4:其实我真实关心的是这些方法是什么时候开始调用的?

 $("#father").on("focusin",function(){
      console.log("father focus in");
	  });
	  var expando=jQuery.expando;
	  var key=document[expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
       $("#child").trigger("focusin");
HTML部分

  <div id="father" style="background-color:red;height:400px;background-color:#ccc;">
       <div id="child" style="height:200px;border:1px solid red;"></div>
 </div>
这时候你去触发trigger,而且触发者是child,那么在jQuery.event. trigger中的eventPath中就会保存如下的部分:

    var result=[div#child, div#father, body, html, document, Window]
接着就会不断的执行eventPath中的元素,从元素中拿出自己的handle函数,也就是通用函数执行

	handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

你应该没有忘记,上面我说过,focusin字段全部保存到了document中(document本身注册了一个focus/blur事件,而且处于捕获阶段),所以当任何一个元素获取焦点那么这个绑定在document捕获阶段的函数就会执行,然后调用了jQuery.event.trigger从而实现从target到window对象的逐层冒泡,从而focus/blur本来不具备冒泡的事件也能够冒泡了;但是,如果是直接调用了子元素的trigger方法呢

 $("html").on("focus","#child",function(){  
		 console.log("html focus in");  
	  }); 
	  //因为下面dispatch指定了只会到body,所以html中的click不会执行!
	  $("body").on("focus","#child",function(){  
		 console.log("html focus in1");  
	  }); 
	$("body").on("focus","#father",function(){  
		 console.log("body focus in2");  
	  }); 
	  $("#child").trigger("focus");
这时候你就会发现,其实直接调用一个元素的trigger也是可以直接冒泡的,之所以是这样,原因在于trigger("focus")就是使得元素具有焦点(其实就是调用elem.focus()),从而调用document对象通过addEventListenert添加的focus/blur回调函数,这个回调函数的作用就是使得focus/blur事件向上冒泡了!

          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 !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
trigger调用的时候document上面的捕获阶段的focus/blur回调也会执行,他的作用在于通过jQuery.event.simulate实现从获取焦点的元素到window元素的逐层冒泡

var handler = function( event ) {
			console.log("lalalalallal");
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};
father上面的回调也会执行,因为他有click事件对应的handleObj数组,还有一个通用的函数!
	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 !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
也就是说下面的代码 获取到了焦点,那么不管是document上面的回调还是father上面的回调都会执行

      $("#father").on("focusin",function(){
      console.log("father focus in");
	  });
	  var expando=jQuery.expando;
	  var key=document[expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
        console.log(data);	
这时候输出"lalalal"同时也打印"father focus in",在jQuery.event.simulate中选择了是用dispatch还是通过trigger来调用
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );//调用trigger
		} else {
			jQuery.event.dispatch.call( elem, e );//调用dispatch所以会执行通过on注册的函数
		}
问题5:我想问问,这时候在document和father上面都保存的有数据?
 $("#father").on("focusin",function(){
      console.log("father focus in");
	  });
	  //father本身的内部数据
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
        console.log(data);	
		//document内部数据
   var expando=jQuery.expando;
	  var key=document[expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
        console.log(data);	
通过 该图你会发现在father元素上和document元素上都有focusin,同时father元素上绑定的是jQuery.event.add中的通用的回调函数!

问题6:我们知道这个特殊事件focus/blur是如何添加和调用的了,那么它在内部是如何被移除的呢?

在jQuery.event.remove中有一段代码

       // Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {//如果移除过后,该类型的事件所对应的handleObj数组已经是空
				//那么调用tearDown来销毁,如果tearDown返回了false那么调用jQuery.removeEvent也就是我们最熟悉的
				//移除事件的方式来移除事件
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}
				delete events[ type ];
			}
		}
表明,如果tearDown返回的值不是false,那么表明就是用tearDown来移除事件的,而focus就是通过tearDown来移除事件的,因为他的tearDown返回值是undefined。那么我们看看tearDown是如何移除事件的

              //很显然我们tearDown没有返回false,所以就是通过原生的JS移除事件来完成的
			//而不用调用jQuery.removeEvent来完成!
			teardown: function() {
				var doc = this.ownerDocument || this,
					//获取document上面绑定的所有的focusin事件
				    //如果focusin事件已经移除到0,那么我们移除添加的通用的函数handle+移除document上面通用的数据
					//如果移除后document上面还存在focusin,那么我们只是修改attaches也就是document上面的内部代理的focusin的数值!
					attaches = jQuery._data( doc, fix ) - 1;
				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
因为focusin是通过setup来添加的独立的回调函数,所以他的移除也是通过对应的tearDown来完成的,因为移除事件必须是相同的函数句柄。tearDown就是判断document上面代理的focusin事件的个数是否为0了,如果是0,表示我们这个通用的事件可以被移除了,因为他没有代理同类的事件了,所以他的回调函数已经没有存在的意义了。移除事件后,我们也要销毁document上面的内部数据,因为document没有代理,所以没有存在的意义了。所以,如果代理的事件个数是0,那么我们 移除通用事件+移除document内部数据!
问题7:focusin和focusout都是同样的步骤,都是通过tearDown和setup来完成的?

 解答:对的。他们对应的是同一个tearDown和setup

if ( !support.focusinBubbles ) {
	//解决focus和blur不冒泡的情况
	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 );
			};
			//添加focusin,focusout!
		jQuery.event.special[ fix ] = {
			// elem, data, namespaces, eventHandle
			setup: function() {
				var doc = this.ownerDocument || this,
					//在document上面添加focusin和focusout事件!
					attaches = jQuery._data( doc, fix );
				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
			},
			//很显然我们tearDown没有返回false,所以就是通过原生的JS移除事件来完成的
			//而不用调用jQuery.removeEvent来完成!
			teardown: function() {
				var doc = this.ownerDocument || this,
					//获取document上面绑定的所有的focusin事件
				    //如果focusin事件已经移除到0,那么我们移除添加的通用的函数handle+移除document上面通用的数据
					//如果移除后document上面还存在focusin,那么我们只是修改attaches也就是document上面的内部代理的focusin的数值!
					attaches = jQuery._data( doc, fix ) - 1;
				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
		};
	});
通过这个例子,如果元素获取了焦点就是调用这里和setup和tearDown对应的通用的回调函数handle

  $("#father").on("focusin",function(){
      console.log("father focus in");
	  });
	  //father本身的内部数据
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];//上面的focusin表示自己为document对象添加了内部数据focusin!
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
问题8:IE浏览器的checkbox和radio是不会冒泡的?

 <div id="father" style="background-color:#ccc;">
        男:<input type="checkbox" id="man" name="sex" style="border:1px solid red;">
	    女:<input type="checkbox" id="female" name="sex" style="border:1px solid red;">

 </div>
JS部分

 document.getElementById("father").onchange=function()  
       {  
   alert("change!");//IE中radio和checkbox是不冒泡的!  
   }
HTML部分

<div id="father" style="background-color:#ccc;">
        男:<input type="checkbox" id="man" name="sex" style="border:1px solid red;">
	    女:<input type="checkbox" id="female" name="sex" style="border:1px solid red;">

 </div>
这时候在IE中,修改checkbox的值father元素的回调函数不会执行,因为IE中change事件不会冒泡。
HTML不变,JS变为:

 $("#father").on("change",function()
	  {
	    console.log("change checkbox");
	  });
	  //这时候father是div元素不是checkbox和radio所以添加了beforeactivate
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	  console.log(data);
通过 该图,你会发现在father这个元素的仓库里面添加了beforeactive和change两个事件,虽然他是代理的checkbox的change事件,但是他不是checkbox,所以只会添加beforeactive事件。

jQuery.event.special.change = {
		//我们用自定义事件来处理这种在IE中不冒泡的行为!
		// elem, data, namespaces, eventHandle
		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;
			// 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 );
			}
		},
        //移除事件的函数!
		teardown: function() {
			jQuery.event.remove( this, "._change" );
			return !rformElems.test( this.nodeName );
		}
	};
同时你要注意,因为添加的是beforeactive,所以他的返回值是undefined,因此他也是通过setup添加的事件,而不是通过原生的JS方式添加的事件。

问题9:这时候已经为father注册了beforeactive事件了(代理元素为div,而不是直接绑定到checkbox),但是这个事件是如何被调用的呢?这毕竟是自定义事件啊?

我们看看father的change事件在jQuery.event.dispatch中是怎么调用的

ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ).apply( matched.elem, args );
因为change事件是特殊事件,所以调用他的handle,而在change特殊事件中handle如下

	handle: function( event ) {
			var elem = event.target;
            // console.log(this);这里面的this是父元素father,而target是man的元素
			// 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 );
				//这里是回调了我们自己设置的回调函数
			}
		}
如果delegate元素是非checkbox和radio,那么他是如何处理的

// Delegated event; lazy-add a change handler on descendant inputs
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
			  //我们手动可以触发他,因为父元素有beforeactivate._change事件,我们可以让子元素触发
				var elem = e.target;
				//这时候target是checkbox!
				//var rformElems = /^(?:input|select|textarea)$/i
				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
					//为target元素添加change._change事件,同时添加changeBubbles为true!
					jQuery.event.add( elem, "change._change", function( event ) {
						//如果触发了子元素的change._change事件,我们触发父元素的change事件
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event, true );
						}
					});
					jQuery._data( elem, "changeBubbles", true );
				}
			});
		}
这表明,如果代理对象是div等元素,那么就是在代理对象上面添加beforeactivate事件,其中命名空间是在_before上
如果我们通过子元素触发这个事件,如:

	  $("#man").trigger("beforeactivate._change");
触发了father元素上面的beforeactivate_change事件后,如果target元素是input,select或者textarea,同时他的changeBubbles是undefined,那么我们给target元素添加一个回调函数,这个回调函数如果通过下面的方式触发

	  $("#man").trigger("change._change");
那么就会触发父元素的change事件,同时给target元素的changeBubbles设置为true!但是我们知道这时候调用了jQuery.event.add但是change事件是特殊事件,所以还是调用了setup,所以该checkbox就具有了click,propertychange,change事件,见 该图
setup: function() {
			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" ) {
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {
							this._just_changed = true;
						}
					});
					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;
			}
问题10:如何解决IE的change事件不冒泡的情况呢?

 $("#father").on("change",function(e)//添加了change事件,用来代理下面的checkbox的change事件
	  {
	      console.log(this);
	  });
	  var expando=jQuery.expando;
	  var key=$("#father")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
	   console.log(data);
	  $("#man").trigger("change");
通过手动触发子元素的trigger来调用就可以了!因为我把代理函数绑定在father元素上面,当trigger传入change事件时候,我们就回调father元素对于的handle,此时this就是father元素。handle中回调的是我们传入的回调函数

		var elem = event.target;
            // console.log(this);这里面的this是父元素father,而target是man的元素
			// 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 );
				//这里是回调了我们自己设置的回调函数
			}
		}
问题11:解决IE的change不冒泡,我们解决了以后如果要删除怎么办?

   teardown: function() {
			jQuery.event.remove( this, "._change" );
			return !rformElems.test( this.nodeName );
		}
问题12:如何解决IE中submit事件不能被代理的情况?

  $("body")[0].onsubmit=function(e)
  {
    alert("form submit");
  }
IE中表单的submit事件不能被代理。
我们通过把submit代理到body上面

 $("body").on("submit",function()
  {
    alert("submit");
  });
    var expando=jQuery.expando;
	  var key=$("body")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
      console.log(data);
通过该图,你会发现body上面被绑定了三个事件,分别为click,keypress和submit。
form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;

如果body上面的click._submit keypress._submit submit只要有一个被调用了,那么


这段代码表示,如果是input或者button,那么可以获取他的form属性,该属性保存了input元素所在的form的所有信息,如 该图
我们为该input元素所在的form元素添加一个事件submit,该事件的命名空间是_submit,同时添加内部数据submitBubbles

// 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)
				//回调函数的时候会查看真正发生submit事件的元素是什么,如果是input或者button那么获取他的form元素
				//如果form元素没有submitBubbles内部数据,把么为该form添加一个submit事件,命名空间是_submit,如果
				//form自己添加的submit事件被调用了,那么为event添加_submit_bubble为true!
				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 );
				}
			});
我们让input元素触发事件click_submit事件

 $("body").on("submit",function()
  {
    alert("submit");
  });
  $("#text").trigger("click._submit");//注意:触发该事件的是input元素
    var expando=jQuery.expando;
	  var key=$("#form")[0][expando];
	  var walhouse=jQuery.cache;
	  var data=walhouse[key];
      console.log(data);
通过 该图你会发现,如果我们用input去触发了click_submit等body上面的自定义事件,这时候form上就会有数据,为submit._submit事件和submitBubbles!
到了这里我们有必要回忆一下命名空间的:

//如果是click事件,那么我们必须知道,所有的click事件,不管什么命名空间都会调用
  $("body").on("click.test1.qinliang",function()
  {
    console.log("submit");
  });
  $("#man").trigger("click");
那么我们看看这里是如何利用命名空间来做文章来实现submit事件冒泡的。

jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
				// Node name check avoids a VML-related crash in IE (#9807)
				//回调函数的时候会查看真正发生submit事件的元素是什么,如果是input或者button那么获取他的form元素
				//如果form元素没有submitBubbles内部数据,把么为该form添加一个submit事件,命名空间是_submit,如果
				//form自己添加的submit事件被调用了,那么为event添加_submit_bubble为true!
				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 ) {
						console.log("form 回调");
						event._submit_bubble = true;
					});
					jQuery._data( form, "submitBubbles", true );
				}
			});
我们在把click_sumbit和keypress_submit事件全部封装到body上面,同时还在body上面封装了submit事件。那么,我们在表单里面任何操作,如点击按钮,文本框输入文本都会触发keypress和click事件全部冒泡到body上面进行处理,他的处理就是后面的这个回调函数
                     function( e ) {
				// Node name check avoids a VML-related crash in IE (#9807)
				//回调函数的时候会查看真正发生submit事件的元素是什么,如果是input或者button那么获取他的form元素
				//如果form元素没有submitBubbles内部数据,把么为该form添加一个submit事件,命名空间是_submit,如果
				//form自己添加的submit事件被调用了,那么为event添加_submit_bubble为true!
				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 );
				}
			});
但是只要用户点击了input元素或者button元素,那么我们就会在form元素上面注册submit_submit事件,用于处理提交表单操作,因为form是可以接受到subit元素的提交事件的。可是,这时候submit还没有到body上面啊,body上面还注册的有submit事件啊,怎么办,往上寻找eventPath就可以了!

如何销毁代理的IE的submit事件

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" );
		}
	};

你可能感兴趣的:(jQuery源码分析之tearDown和setup)