问题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是代理元素那么获取代理,那么获取代理类型的特殊的对象!
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}!
//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内部数据!
解答:对的。他们对应的是同一个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事件不会冒泡。
$("#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事件不能被代理。
$("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只要有一个被调用了,那么
// 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" ); } };