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); } };
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。