jQuery-1.9.1源码分析系列(十)事件系统——事件绑定

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

事件绑定的方式有很多种。使用了jQuery那么原来那种绑定方式(elem.click = function(){...})就不推荐了,原因?

最主要的一个原因是elem.click = fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果。

看一下jQuery绑定事件的方式有哪些

jQuery.fn.eventType([[data,] fn])

比如eventType指的是事件类型,比如click: $("#chua").click(fn);

data这个参数一般都不会使用。这种方式事件绑定在("#chua")上,没有委托事件,和js原生的事件绑定更接近。我们看一下源码

jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +

"mousedown mouseup mousemove mouSEOver mouseout mouseenter mouseleave " +

"change select submit keydown keyPRess keyup error contextmenu").split(" "),function( i, name ) {//合并15种事件统一增加到jQuery.fn上,内部调用this.on / this.triggerjQuery.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的口号Write less, do more的代价吧。最终使用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,//For use in libraries implementing .is(). We use this for POS matching in `select`//"needsContext": new RegExp( "^" + 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 ) dispatched jQuery.EventhandleObj =types.handleObj;//types.delegateTarget是事件托管对象jQuery( types.delegateTarget ).off(//组合jQuery识别的typehandleObj.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 ) dispatched jQuery.EventhandleObj =types.handleObj;//types.delegateTarget是事件托管对象jQuery( types.delegateTarget ).off(//组合jQuery识别的typehandleObj.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 );

});

}

View Code

接下来分析一下事件解绑的低级api jQuery.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 ) {//例如 var js_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还检事件对象是否为空,所以使用它替代deletejQuery._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 );

}

};

如果觉得本文不错,请点击右下方【推荐】!

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航