您的位置:首页 > Web前端 > JQuery

jQuery源码分析之tearDown和setup

2015-12-14 20:03 711 查看
问题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" );
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: