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

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

2015-10-17 16:45 621 查看
参见博客:点击打开链接

首先观看该图

问题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").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事件!

我们看看这个事件在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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: