参见博客:点击打开链接
首先观看该图
问题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的处理!
$("#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本身的机制
$("#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").onchange=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事件!
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], 所以同类的事件全部被调用,所以实现了冒泡。
// 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);