jquery事件绑定解绑机制源码解析
引子
为什么Jquery能实现不传回调函数也能解绑事件?如下:
$("p").on("click",function(){
alert("Theparagraphwasclicked.");
});
$("#box1").off("click");
事件绑定解绑机制
调用on函数的时候,将生成一份事件数据,结构如下:
{
type:type,
origType:origType,
data:data,
handler:handler,
guid:guid,
selector:selector,
needsContext:needsContext,
namespace:namespace
}
并将该数据加入到元素的缓存中。jquery中每个元素都可以有一个缓存(只有有需要的时候才生成),其实就是该元素的一个属性。jquery为每个元素的每种事件都建立一个队列,用来保存事件处理函数,所以可以对一个元素添加多个事件处理函数。缓存的结构如下:
"div#box":{//元素
"Jquery623873":{//元素的缓存
"events":{
"click":[
{//元素click事件的事件数据
type:type,
origType:origType,
data:data,
handler:handler,
guid:guid,
selector:selector,
needsContext:needsContext,
namespace:namespace
}
],
"mousemove":[
{
type:type,
origType:origType,
data:data,
handler:handler,
guid:guid,
selector:selector,
needsContext:needsContext,
namespace:namespace
}
]
}
}
}
当要解绑事件的时候,如果没指定fn参数,jquery就会从该元素的缓存里拿到要解绑的事件的处理函数队列,从里面拿出fn参数,然后调用removeEventListener进行解绑。
源代码
代码注释可能不太清楚,可以复制出来看
jquery原型中的on,one,off方法:
事件绑定从这里开始
jQuery.fn.extend({
on:function(types,selector,data,fn){
returnon(this,types,selector,data,fn);
},
one:function(types,selector,data,fn){
returnon(this,types,selector,data,fn,1);
},
off:function(types,selector,fn){
//此处省略处理参数的代码
returnthis.each(function(){
jQuery.event.remove(this,types,fn,selector);
});
}
});
独立出来供one和on调用的on函数:
functionon(elem,types,selector,data,fn,one){
varorigFn,type;
//此处省略处理参数的代码
//是否是通过one绑定,是的话使用一个函数代理当前事件回调函数,代理函数只执行一次
//这里使用到了代理模式
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++);
}
/************************************************
***jquery将所有选择到的元素到放到一个数组里,然后
***对每个元素到使用event对象的add方法绑定事件
*************************************************/
returnelem.each(function(){
jQuery.event.add(this,types,fn,data,selector);
});
}
处理参数的代码也可以看一下,实现on("click",function(){})这样调用on:function(types,selector,data,fn)也不会出错。其实就是内部判断,如果data,fn参数为空的时候,把selector赋给fn
event对象是事件绑定的一个关键对象:
这里处理把事件绑定到元素和把事件信息添加到元素缓存的工作:
jQuery.event={
add:function(elem,types,handler,data,selector){
varhandleObjIn,eventHandle,tmp,
events,t,handleObj,
special,handlers,type,namespaces,origType,
elemData=dataPriv.get(elem);//这句将检查elem是否被缓存,如果没有将会创建一个缓存添加到elem元素上。形式诸如:elem["jQuery310057655476080253721"]={}
//Don'tattacheventstonoDataortext/commentnodes(butallowplainobjects)
if(!elemData){
return;
}
//用户可以传入一个自定义数据对象来代替事件回调函数,将事件回调函数放在这个数据对象的handler属性里
if(handler.handler){
handleObjIn=handler;
handler=handleObjIn.handler;
selector=handleObjIn.selector;
}
//每个事件回调函数都会生成一个唯一的id,以后find/remove的时候会用到
if(!handler.guid){
handler.guid=jQuery.guid++;
}
//如果元素第一次绑定事件,则初始化元素的事件数据结构和主回调函数(main)
//说明:每个元素有一个主回调函数,作为绑定多个事件到该元素时的回调的入口
if(!(events=elemData.events)){
events=elemData.events={};
}
//这里就是初始化主回调函数的代码
if(!(eventHandle=elemData.handle)){
eventHandle=elemData.handle=function(e){
//DiscardthesecondeventofajQuery.event.trigger()and
//whenaneventiscalledafterapagehasunloaded
returntypeofjQuery!=="undefined"&&jQuery.event.triggered!==e.type?
jQuery.event.dispatch.apply(elem,arguments):undefined;
};
}
//处理事件绑定,考虑到可能会通过空格分隔传入多个事件,这里要进行多事件处理
types=(types||"").match(rnotwhite)||[""];
t=types.length;
while(t--){
tmp=rtypenamespace.exec(types[t])||[];
type=origType=tmp[1];
namespaces=(tmp[2]||"").split(".").sort();
//There*must*beatype,noattachingnamespace-onlyhandlers
if(!type){
continue;
}
//Ifeventchangesitstype,usethespecialeventhandlersforthechangedtype
special=jQuery.event.special[type]||{};
//Ifselectordefined,determinespecialeventapitype,otherwisegiventype
type=(selector?special.delegateType:special.bindType)||type;
//Updatespecialbasedonnewlyresettype
special=jQuery.event.special[type]||{};
//事件回调函数的数据对象
handleObj=jQuery.extend({
type:type,
origType:origType,
data:data,
handler:handler,
guid:handler.guid,
selector:selector,
needsContext:selector&&jQuery.expr.match.needsContext.test(selector),
namespace:namespaces.join(".")
},handleObjIn);
//加入第一次绑定该类事件,会初始化一个数组作为事件回调函数队列,每个元素的每一种事件有一个队列
if(!(handlers=events[type])){
handlers=events[type]=[];
handlers.delegateCount=0;
//OnlyuseaddEventListenerifthespecialeventshandlerreturnsfalse
if(!special.setup||
special.setup.call(elem,data,namespaces,eventHandle)===false){
if(elem.addEventListener){
elem.addEventListener(type,eventHandle);
}
}
}
if(special.add){
special.add.call(elem,handleObj);
if(!handleObj.handler.guid){
handleObj.handler.guid=handler.guid;
}
}
//加入到事件回调函数队列
if(selector){
handlers.splice(handlers.delegateCount++,0,handleObj);
}else{
handlers.push(handleObj);
}
//Keeptrackofwhicheventshaveeverbeenused,foreventoptimization
//用来追踪哪些事件从未被使用,用以优化
jQuery.event.global[type]=true;
}
}
};
千万注意,对象和数组传的是引用!比如将事件数据保存到缓存的代码:
handlers=events[type]=[];
if(selector){
handlers.splice(handlers.delegateCount++,0,handleObj);
}else{
handlers.push(handleObj);
}
handlers的改变,events[type]会同时改变。
dataPriv就是管理缓存的对象:
其工作就是给元素创建一个属性,这个属性是一个对象,然后把与这个元素相关的信息放到这个对象里面,缓存起来。这样需要使用到这个对象的信息时,只要知道这个对象就可以拿到:
functionData(){
this.expando=jQuery.expando+Data.uid++;
}
Data.uid=1;
//删除部分没用到代码
Data.prototype={
cache:function(owner){
//取出缓存,可见缓存就是目标对象的一个属性
varvalue=owner[this.expando];
//如果对象还没有缓存,则创建一个
if(!value){
value={};
//Wecanacceptdatafornon-elementnodesinmodernbrowsers,
//butweshouldnot,see#8335.
//Alwaysreturnanemptyobject.
if(acceptData(owner)){
//Ifitisanodeunlikelytobestringify-edorloopedover
//useplainassignment
if(owner.nodeType){
owner[this.expando]=value;
//Otherwisesecureitinanon-enumerableproperty
//configurablemustbetruetoallowthepropertytobe
//deletedwhendataisremoved
}else{
Object.defineProperty(owner,this.expando,{
value:value,
configurable:true
});
}
}
}
returnvalue;
},
get:function(owner,key){
returnkey===undefined?
this.cache(owner):
//AlwaysusecamelCasekey(gh-2257)驼峰命名
owner[this.expando]&&owner[this.expando][jQuery.camelCase(key)];
},
remove:function(owner,key){
vari,
cache=owner[this.expando];
if(cache===undefined){
return;
}
if(key!==undefined){
//Supportarrayorspaceseparatedstringofkeys
if(jQuery.isArray(key)){
//Ifkeyisanarrayofkeys...
//WealwayssetcamelCasekeys,soremovethat.
key=key.map(jQuery.camelCase);
}else{
key=jQuery.camelCase(key);
//Ifakeywiththespacesexists,useit.
//Otherwise,createanarraybymatchingnon-whitespace
key=keyincache?
[key]:
(key.match(rnotwhite)||[]);
}
i=key.length;
while(i--){
deletecache[key[i]];
}
}
//Removetheexpandoifthere'snomoredata
if(key===undefined||jQuery.isEmptyObject(cache)){
//Support:Chrome<=35-45
//Webkit&Blinkperformancesufferswhendeletingproperties
//fromDOMnodes,sosettoundefinedinstead
//https://bugs.chromium.org/p/chromium/issues/detail?id=378607(bugrestricted)
if(owner.nodeType){
owner[this.expando]=undefined;
}else{
deleteowner[this.expando];
}
}
},
hasData:function(owner){
varcache=owner[this.expando];
returncache!==undefined&&!jQuery.isEmptyObject(cache);
}
};
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。