jquery事件核心源码分析
我们从绑定事件开始,一步步往下看:
以jquery.1.8.3为例,平时通过jquery绑定事件最常用的是on方法,大概分为下面3种类型:
复制代码
$(target).on(''click'',function(){
//函数内容
})
$(target).on(''click'',''.child'',function(){
//函数内容
})
$(target).on({
click:function(){},
mouseover:function(){},
mouseout:function(){}
})
复制代码
第一种是我们最常用的写法,通过元素选择器,直接绑定事件;
第二种则是利用了事件委托原理,由最初的父元素代理子元素的事件,动态添加的元素绑定事件用第一种方法时无效的;
第三种则是同一元素同时绑定多个事件时的简略写法。
我们来看一下on方法的源码,如果我们想封装插件类似on方法调用,可以像on一样来书写,具体可见另一篇文章jQuery插件开发(溢出滚动)
复制代码
on:function(types,selector,data,fn,/INTERNAL/one){
varorigFn,type;
//Typescanbeamapoftypes/handlers
//上述第三种用法,传入第一个参数为object
if(typeoftypes==="object"){
//(types-Object,selector,data)
if(typeofselector!=="string"){//&&selector!=null
//(types-Object,data)
data=data||selector;
selector=undefined;
}
for(typeintypes){
this.on(type,selector,data,types[type],one);
}
returnthis;
}
if(data==null&&fn==null){
//(types,fn)
fn=selector;
data=selector=undefined;
}elseif(fn==null){
if(typeofselector==="string"){
//(types,selector,fn)
fn=data;
data=undefined;
}else{
//(types,data,fn)
fn=data;
data=selector;
selector=undefined;
}
}
if(fn===false){
fn=returnFalse;
}elseif(!fn){
returnthis;
}
if(one===1){
origFn=fn;
fn=function(event){
//Canuseanemptyset,sinceeventcontainstheinfo
jQuery().off(event);
returnorigFn.apply(this,arguments);
};
//UsesameguidsocallercanremoveusingorigFn
fn.guid=origFn.guid||(origFn.guid=jQuery.guid++);
}
returnthis.each(function(){
jQuery.event.add(this,types,fn,data,selector);
});
}
复制代码
我们可以看到,on方法内部的代码类似于初始化,通过对传入参数的分析,来矫正type,fn,data,selector等变量,从而正确的调用jquery.event.add方法。jquery.event是事件的核心。
jquery.event代码结构如下:
复制代码
jQuery.event={
add:function(){
},
global:{},
remove:function(){
},
customEvent:function(){
},
trigger:function(){
},
dispatch:function(){
//在老版本的jquery,此方法名为handle
},
props:'''',
fixHooks:{
},
keyHooks:{
},
mouseHooks:{
},
fix:function(){
},
special:function(){
},
simulate:function(){
}
}
复制代码
其中add方法通过一些设置为元素注册添加事件:
所谓的特殊事件指类似于mouseenter,mouseleave,ready事件并不是浏览器所支持的事件,他们不能通过统一的addEventListener/attachEvent来添加这个事件.而是通过setup和teardown来绑定和删除事件,如下:
复制代码
beforeunload:{
setup:function(data,namespaces,eventHandle){
//Weonlywanttodothisspecialcaseonwindows
if(jQuery.isWindow(this)){
this.onbeforeunload=eventHandle;
}
},
teardown:function(namespaces,eventHandle){
if(this.onbeforeunload===eventHandle){
this.onbeforeunload=null;
}
}
}
复制代码
复制代码
add:function(elem,types,handler,data,selector){
varelemData,eventHandle,events,
t,tns,type,namespaces,handleObj,
handleObjIn,handlers,special;
//Don''tattacheventstonoDataortext/commentnodes(allowplainobjectstho)
if(elem.nodeType===3||elem.nodeType===8||!types||!handler||!(elemData=jQuery._data(elem))){
return;
}
//Callercanpassinanobjectofcustomdatainlieuofthehandler
//如果传入的handler包括handler属性,则通过临时变量将handler与selector设置为正确的指向。
if(handler.handler){
handleObjIn=handler;
handler=handleObjIn.handler;
selector=handleObjIn.selector;
}
//MakesurethatthehandlerhasauniqueID,usedtofind/removeitlater
//为每个元素添加一个唯一的guid
if(!handler.guid){
handler.guid=jQuery.guid++;
}
//Inittheelement''seventstructureandmainhandler,ifthisisthefirst
//elemData结构见下面截图
events=elemData.events;
if(!events){
elemData.events=events={};//初次绑定事件
}
eventHandle=elemData.handle;
if(!eventHandle){
//eventHandle经过dispatch处理,已不同于最初传入的handler
elemData.handle=eventHandle=function(e){
//DiscardthesecondeventofajQuery.event.trigger()and
//whenaneventiscalledafterapagehasunloaded
returntypeofjQuery!=="undefined"&&(!e||jQuery.event.triggered!==e.type)?
jQuery.event.dispatch.apply(eventHandle.elem,arguments):
undefined;
};
//AddelemasapropertyofthehandlefntopreventamemoryleakwithIEnon-nativeevents
eventHandle.elem=elem;
}
//Handlemultipleeventsseparatedbyaspace
//jQuery(...).bind("mouseovermouseout",fn);
types=jQuery.trim(hoverHack(types)).split("");
for(t=0;t
tns=rtypenamespace.exec(types[t])||[];
type=tns[1];
namespaces=(tns[2]||"").split(".").sort();
//Ifeventchangesitstype,usethespecialeventhandlersforthechangedtype
special=jQuery.event.special[type]||{};
//Ifselectordefined,determinespecialeventapitype,otherwisegiventype
type=(selector?special.delegateType:special.bindType)||type;
//Updatespecialbasedonnewlyresettype
special=jQuery.event.special[type]||{};
//handleObjispassedtoalleventhandlers
handleObj=jQuery.extend({
type:type,
origType:tns[1],
data:data,
handler:handler,
guid:handler.guid,
selector:selector,
needsContext:selector&&jQuery.expr.match.needsContext.test(selector),
namespace:namespaces.join(".")
},handleObjIn);
//Inittheeventhandlerqueueifwe''rethefirst
handlers=events[type];
if(!handlers){
handlers=events[type]=[];
handlers.delegateCount=0;
//OnlyuseaddEventListener/attachEventifthespecialeventshandlerreturnsfalse
//如果为非special事件则由addeventListener或attachEvent事件绑定,否则择优special.setup绑定
if(!special.setup||special.setup.call(elem,data,namespaces,eventHandle)===false){
//Bindtheglobaleventhandlertotheelement
//当前eventHandle是经过处理的eventHandle
if(elem.addEventListener){
elem.addEventListener(type,eventHandle,false);
}elseif(elem.attachEvent){
elem.attachEvent("on"+type,eventHandle);
}
}
}
if(special.add){
special.add.call(elem,handleObj);
if(!handleObj.handler.guid){
handleObj.handler.guid=handler.guid;
}
}
//Addtotheelement''shandlerlist,delegatesinfront
if(selector){//元素事件为事件委托
handlers.splice(handlers.delegateCount++,0,handleObj);
}else{//绑定于元素本身的事件
handlers.push(handleObj);
}
console.log(elemData)
//Keeptrackofwhicheventshaveeverbeenused,foreventoptimization
jQuery.event.global[type]=true;
}
//NullifyelemtopreventmemoryleaksinIE
elem=null;
},
复制代码
其中注意elemData=jQuery._data(elem)这句,我们简单绑定一个事件,然后看elemData值
$(document).click(function(){
console.log(1)
})
如上左图,最终结果elemData即jquery的缓存数据中主要包含两个属性,events及handle,其中events包含了当前元素注册的所有事件,如click,keydown等,其中每一个事件下面又可以包括多个handler,每个handler有一个唯一的guid,后面触发及删除相应事件函数都要用到这个,events对象还包含一个属性为delegateCount,则记录着该元素总共代理事件的次数。在右图中可以看到在某一个事件下绑定的不同handler,代理事件(selector部位undefined的情况)排在前面,而绑定在元素自身的事件排在代理事件后面。
需要注意的是代码中的elem.addEventListener(type,eventHandle,false)并不同于我们简单的将handler处理函数绑定,而是对handler通过dispatch进行了处理。
另外,在事件函数中,js默认传入的第一个参数为事件对象.
下面我们来看dispatch方法,该方法接受传入的event参数,并对绑定在元素上的事件进行处理:例如我们代码如下
复制代码
点击
复制代码
可以看到,div元素本身绑定有click事件,同时又代理子元素p和a的事件,这样当在div发生点击事件的时候,第一步dispatch会从事件元素的currentTarget开始往上循环遍历直到div元素,将需要触发事件的元素及事件加到handlerQueue数组中(前提是元素本身有代理事件),然后会将绑定在元素本身的事件添加到handlerQueue。经过上面两步的处理,handlerQueue就形成一个需要触发事件的集合,通过这个集合,我们便能正确的响应事件。
复制代码
dispatch:function(event){
//MakeawritablejQuery.Eventfromthenativeeventobject
//通过fix方法对event进行兼容性处理
event=jQuery.event.fix(event||window.event);
vari,j,cur,ret,selMatch,matched,matches,handleObj,sel,related,
handlers=((jQuery._data(this,"events")||{})[event.type]||[]),
delegateCount=handlers.delegateCount,
args=core_slice.call(arguments),
run_all=!event.exclusive&&!event.namespace,
special=jQuery.event.special[event.type]||{},
handlerQueue=[];
//Usethefix-edjQuery.Eventratherthanthe(read-only)nativeevent
args[0]=event;
event.delegateTarget=this;
//CallthepreDispatchhookforthemappedtype,andletitbailifdesired
if(special.preDispatch&&special.preDispatch.call(this,event)===false){
return;
}
//Determinehandlersthatshouldruniftherearedelegatedevents
//Avoidnon-left-clickbubblinginFirefox(#3861)
//火狐右键会触发click事件,但是event.button值为2
//delegateCount不为0代表元素本身有代理其他元素事件
if(delegatewww.baiyuewang.netCount&&!(event.button&&event.type==="click")){
/事件从event.target冒泡到当前元素
#例如元素本身绑定有事件a,而且代理其子元素child事件b及child子元素c事件,
#则点击c元素时,执行事件顺序为c-b-a,即节点层次越深,事件执行优先级越高
/
for(cur=event.target;cur!=this;cur=cur.parentNode||this){
//Don''tprocessclicks(ONLY)ondisabledelements(#6911,#8165,#11382,#11764)
if(cur.disabled!==true||event.type!=="click"){
selMatch={};
matches=[];
//代理事件,delegateCount为代理事件的数量,不同handler事件的顺序见上图中右图,代理事件在上,自身事件在下
for(i=0;i handleObj=handlers[i];
sel=handleObj.selector;
if(selMatch[sel]===undefined){
selMatch[sel]=handleObj.needsContext?
jQuery(sel,this).index(cur)>=0:
jQuery.find(sel,this,null,[cur]).length;
}
if(selMatch[sel]){
matches.push(handleObj);
}
}
if(matches.length){
handlerQueue.push({elem:cur,matches:matches});//委托事件
}
}
}
}
//Addtheremaining(directly-bound)handlers
if(handlers.length>delegateCount){
//自身事件
handlerQueue.push({elem:this,matches:handlers.slice(delegateCount)});
}
//Rundelegatesfirst;theymaywanttostoppropagationbeneathus
//hangdlerQueue是一个集合元素自身事件及代理子元素事件的数组
//例如html结构为,当点击范围在p同时不在a内时,则会执行p和div的事件,
//相对应的handlerQuesu中并不包含a
for(i=0;i matched=handlerQueue[i];
event.currentTarget=matched.elem;
for(j=0;j handleObj=matched.matches[j];
//Triggeredeventmusteither1)benon-exclusiveandhavenonamespace,or
//2)havenamespace(s)asubsetorequaltothoseintheboundevent(bothcanhavenonamespace).
if(run_all||(!event.namespace&&!handleObj.namespace)||event.namespace_re&&event.namespace_re.test(handleObj.namespace)){
event.data=handleObj.data;
event.handleObj=handleObj;
ret=((jQuery.event.special[handleObj.origType]||{}).handle||handleObj.handler)
.apply(matched.www.wang027.comelem,args);
if(ret!==undefined){
event.result=ret;
if(ret===false){
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
//CallthepostDispatchhookforthemappedtype
if(special.postDispatch){
special.postDispatch.call(this,event);
}
returnevent.result;
},
复制代码
具体如上所示,源码都做了相应备注,其中handlerQueue结构如下,前两项为代理事件,最后一项为元素本身事件,matches为当前元素handler集合。
其中fix函数用于对事件对象的修正,首先构建一个新的可扩展的event对象,在jquery.event中还包含props,fixHooks,keyHooks,mouseHooks,分别存储了事件对象的公共属性,键盘事件属性,鼠标事件属性等,根据事件类型为新构建event对象赋予新的属性,同时我们在后期扩展时也可为该event对象赋予自定义属性。
复制代码
fix:function(event){
if(event[jQuery.expando]){
returnevent;
}
//Createawritablecopyoftheeventobjectandnormalizesomeproperties
vari,prop,
originalEvent=event,
fixHook=jQuery.event.fixHooks[event.type]||{},
copy=fixHook.props?this.props.concat(fixHook.props):this.props;
event=jQuery.Event(originalEvent);
for(i=copy.length;i;){
prop=copy[--i];
event[prop]=originalEvent[prop];
}
//Fixtargetproperty,ifnecessary(#1925,IE6/7/8&Safari2)
if(!event.target){
event.target=originalEvent.srcElement||document;
}
//Targetshouldnotbeatextnode(#504,Safari)
if(event.target.nodeType===3){
event.target=event.target.parentNode;
}
//Formouse/keyevents,metaKey==falseifit''sundefined(#3368,#11328;IE6/7/8)
event.metaKey=!!event.metaKey;
returnfixHook.filter?fixHook.filter(event,originalEvent):event;
},
复制代码
当然jquery.event还有trigger,remove,simulate等其他方法,在此就不一一列举,基本思路都是一致的。对以上原理理解透了,就可以自己根据需要来扩展jquery方法,如mousewheel事件,我们可以利用fix方法来完成对event对象的扩展,而不用自己重新写一套兼容性的代码,具体下节再分析。
文中如有错误及不当之处,请及时指出,谢谢!
文中所用jquery版本为1.8.3。1.2.6版本的jquery事件核心更容易理解。当然里面缺少事件代理的处理。
|
|