配色: 字号:
jquery事件核心源码分析
2016-12-15 | 阅:  转:  |  分享 
  
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事件核心更容易理解。当然里面缺少事件代理的处理。

献花(0)
+1
(本文系thedust79首藏)