jQuery 1.9.1源码分析系列(十)事件系统之绑定事件
事件绑定的方法有很多种,使用了jquery那么原理那种绑定方式(elem.click=function(){...}))就不太想推荐给大家了。最主要的原因是elem.click=fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果。
下面给大家介绍jquery绑定事件的方式有哪些吧。
jQuery.fn.eventType([[data,]fn])
比如eventType指的是事件类型,比如click:$("#chua").click(fn);
data这个参数一般都不会使用。这种方式事件绑定在("#chua")上,没有委托事件,和js原生的事件绑定更接近。我们看一下源码
jQuery.each(("blurfocusfocusinfocusoutloadresizescrollunloadclickdblclick"+ "mousedownmouseupmousemovemouseovermouseoutmouseentermouseleave"+ "changeselectsubmitkeydownkeypresskeyuperrorcontextmenu").split(""),function(i,name){ //合并15种事件统一增加到jQuery.fn上,内部调用this.on/this.trigger jQuery.fn[name]=function(data,fn){ returnarguments.length>0? this.on(name,null,data,fn): //如果不带参数表示立刻触发指定事件 this.trigger(name); }; }); jQuery.fn.bind(types[,data],fn)
比如$("#chua").bind("click",fn)。直接将事件绑定到$("#chua")上,没有委托事件。源码
bind:function(types,data,fn){ returnthis.on(types,null,data,fn); }, unbind:function(types,fn){ returnthis.off(types,null,fn); } jQuery.fn.delegate(selector,types[,data],fn)
顾名思义delegate这个函数是用来做事件委托的,将选择器selector对应的响应处理委托给当前jQuery所匹配的元素。
比如:$(document).delegate('#big',"click",dohander);分析到这里顺便分析一下事件委托的处理流程。
当点击"#big"元素的时候,事件click会冒泡直到document节点;
document绑定了处理事件,这个处理事件会调用到事件分发器dispatch;
dispatch中取出对应事件类型click的所有的委托事件列表handlers;
根据事件源event.target过滤出委托事件列表handlers中每一个元素的selector属性对应的节点处于事件原和委托节点document之间(包括事件源)的委托事件,保存为handlerQueue;
执行handlerQueue里面的事件处理。
上面是一个大致的流程,后续会详细分析。先看delegate源码
delegate:function(selector,types,data,fn){ returnthis.on(types,selector,data,fn); }, undelegate:function(selector,types,fn){ //(namespace)or(selector,types[,fn]) returnarguments.length===1?this.off(selector,"**"):this.off(types,selector||"**",fn); } jQuery.fn.one(types[,selector[,data]],fn)
通过one()函数绑定的事件处理函数都是一次性的,只有首次触发事件时会执行该事件处理函数。触发之后,jQuery就会移除当前事件绑定。
比如$("#chua").one("click",fn);为#chua节点绑定一次性的click事件
$(document).one("click","#chua",fn);将#chua的click事件委托给document处理。源码
one:function(types,selector,data,fn){ returnthis.on(types,selector,data,fn,1); } jQuery.fn.trigger(type[,data]) jQuery.fn.triggerHandler(type[,data])
trigger触发jQuery对象所匹配的每一个元素对应type类型的事件。比如$("#chua").trigger("click");
triggeHandler只触发jQuery对象所匹配的元素中的第一个元素对应的type类型的事件,且不会触发事件的默认行为。
//立刻触发jQuery对象内所有元素的指定type的事件 trigger:function(type,data){ returnthis.each(function(){ jQuery.event.trigger(type,data,this); }); }, //立刻触发jQuery对象内第一个元素的指定type的事件,且不会触发事件(比如表单提交)的默认行为 triggerHandler:function(type,data){ varelem=this[0]; if(elem){ returnjQuery.event.trigger(type,data,elem,true); } }
上面分析了那么些个事件绑定,有么有发现他们都是使用.on方式绑定的?这也是为什么提倡统一使用on来绑定的原因(one方式除外)。
jQuery.fn.on(types[,selector[,data]],fn)
.on的事件绑定一半的代码都实在处理传递不同参数的处理,这也是jQuery的口号Writeless,domore的代价吧。最终使用jQuery.event.add来绑定事件。
jQuery.event.add绑定事件有几个比较关键的地方:
第一个,使用内部缓存来保存节点elem的事件信息
//获取缓存数据 elemData=jQuery._data(elem); ... //设置缓存数据 if(!(events=elemData.events)){ events=elemData.events={}; } if(!(eventHandle=elemData.handle)){ eventHandle=elemData.handle=function(e){ ... }; //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露 eventHandle.elem=elem; }
第二个,设置绑定事件信息,特别是指定的选择器selector、响应处理handler、响应事件类型type、命名空间namespace
//handleObj:设置绑定事件信息。贯穿整个事件处理 handleObj=jQuery.extend({ type:type, origType:origType, data:data, handler:handler, guid:handler.guid, selector:selector, //Foruseinlibrariesimplementing.is().WeusethisforPOSmatchingin`select` //"needsContext":newRegExp("^"+whitespace+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ //whitespace+"*((?:-\\d)?\\d*)"+whitespace+"*\\)|)(?=[^-]|$)","i") //用来判断亲密关系 needsContext:selector&&jQuery.expr.match.needsContext.test(selector), namespace:namespaces.join(".") },handleObjIn);
第三个,节点的事件列表中,真正的委托事件列表放置在前面,和delegateCount属性同步,即events.click.length假设为3,events.click.delegateCount假设为2。那么events.click[0]和events.click[1]所指定事件是委托事件。第三个events.click[2]对应的事件不是委托事件,而是节点自身的事件。
//将事件对象handleObj添加到元素的处理列表,委托事件放在前面,委托代理计数递增 if(selector){ handlers.splice(handlers.delegateCount++,0,handleObj); }else{ handlers.push(handleObj); }
源码和添加事件后的结构上一章已经分析,详情请点击查看
绑定有一个公用函数jQuery.fn.on。解绑同样有一个公用函数jQuery.fn.off
jQuery.fn.off([types[,selector][,fn]])
这里的传参有个比较特殊的情况:当types是浏览器事件对象event的时候,表示要去掉(解绑)委托节点上event.selector指定的委托事件
//传入的参数是事件且绑定了处理函数 if(types&&types.preventDefault&&types.handleObj){ //(event)dispatchedjQuery.Event handleObj=types.handleObj; //types.delegateTarget是事件托管对象 jQuery(types.delegateTarget).off( //组合jQuery识别的type handleObj.namespace?handleObj.origType+"."+handleObj.namespace:handleObj.origType, handleObj.selector, handleObj.handler ); returnthis; }
无论如何最终都是调用jQuery.event.remove函数来解绑事件。
jQuery.fn.off完整的源码如下
off:function(types,selector,fn){ varhandleObj,type; //传入的参数是事件且绑定了处理函数 if(types&&types.preventDefault&&types.handleObj){ //(event)dispatchedjQuery.Event handleObj=types.handleObj; //types.delegateTarget是事件托管对象 jQuery(types.delegateTarget).off( //组合jQuery识别的type handleObj.namespace?handleObj.origType+"."+handleObj.namespace:handleObj.origType, handleObj.selector, handleObj.handler ); returnthis; } if(typeoftypes==="object"){ //(types-object[,selector]) for(typeintypes){ this.off(type,selector,types[type]); } returnthis; } if(selector===false||typeofselector==="function"){ //(types[,fn]) fn=selector; selector=undefined; } if(fn===false){ fn=returnFalse; } returnthis.each(function(){ jQuery.event.remove(this,types,fn,selector); }); }
接下来分析一下事件解绑的低级apijQuery.event.remove。
jQuery.event.remove
jQuery使用.off()函数伤处绑定的事件时内部调用的基础函数是jQuery.event.remove。该函数的处理流程如下
1.分解传入的要删除的事件类型types,遍历类型,如果要删除的事件没有事件名,只有命名空间则表示删除该命名空间下所有绑定事件
//分解types为type.namespace为单位元素的数组 types=(types||"").match(core_rnotwhite)||[""]; t=types.length; while(t--){ tmp=rtypenamespace.exec(types[t])||[]; type=origType=tmp[1]; namespaces=(tmp[2]||"").split(".").sort(); //解绑当前元素的当前命名空间(types[t])上所有的事件 if(!type){ for(typeinevents){ jQuery.event.remove(elem,type+types[t],handler,selector,true); } continue; } ...
2.遍历类型过程中,删除匹配的事件,代理计数修正
type=(selector?special.delegateType:special.bindType)||type; handlers=events[type]||[]; tmp=tmp[2]&&newRegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"); //删除匹配事件 origCount=j=handlers.length; while(j--){ handleObj=handlers[j]; //各种满足移除事件的条件才能移除 if((mappedTypes||origType===handleObj.origType)&& (!handler||handler.guid===handleObj.guid)&& (!tmp||tmp.test(handleObj.namespace))&& (!selector||selector===handleObj.selector||selector==="**"&&handleObj.selector)){ handlers.splice(j,1); if(handleObj.selector){ handlers.delegateCount--; } if(special.remove){ special.remove.call(elem,handleObj); } } }
3.如果节点上指定类型的事件处理器已经为空,则将events上的该类型的事件处理对象移除
//移除事件处理对象 //(移除特殊事件处理过程中避免潜在的无限递归,下一章会专门详解这种情况) if(origCount&&!handlers.length){ //例如varjs_obj=document.createElement("div");js_obj.onclick=function(){…} //上面的js_obj是一个DOM元素的引用,DOM元素它长期在网页当中,不会消失,而这个DOM元素的一属性onclick,又是内部的函数引用(闭包),而这个匿名函数又和js_obj之间有隐藏的关联(作用域链)所以形成了一个,循环引用. if(!special.teardown||special.teardown.call(elem,namespaces,elemData.handle)===false){ jQuery.removeEvent(elem,type,elemData.handle); } deleteevents[type]; }
4.如果节点上没有任何绑定的事件,则清空事件处理入口handle
if(jQuery.isEmptyObject(events)){ deleteelemData.handle; //removeData还检事件对象是否为空,所以使用它替代delete jQuery._removeData(elem,"events"); }
拓展:浏览器事件删除jQuery.removeEvent
jQuery.removeEvent=document.removeEventListener? function(elem,type,handle){ if(elem.removeEventListener){ elem.removeEventListener(type,handle,false); } }: function(elem,type,handle){ varname="on"+type; if(elem.detachEvent){ //#8545,#7054,避免自定义事件在IE6-8中的内存泄露 //detachEvent需要传递第一个参数,不能是undefined的 if(typeofelem[name]===core_strundefined){ elem[name]=null; } elem.detachEvent(name,handle); } };
以上内容是小编给大家介绍的jQuery1.9.1源码分析系列(十)事件系统之绑定事件,希望大家喜欢。