jQuery源码分析系列:事件
jQuery 事件处理:
首先绑定事件最终的操作时:
1.addEventListener的使用方式:
1 target.addEventListener(type, listener, useCapture);
target: 文档节点、document、window 或 XMLHttpRequest。
type: 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
useCapture :是否使用捕捉,一般用 false 。
例如:
1 document.getElementById("testText").addEventListener("keydown", function (event) { alert(event.keyCode); }, false);
IE中:
2.attachEvent的使用方式:
1 target.attachEvent(type, listener);
target: 文档节点、document、window 或 XMLHttpRequest。
type: 字符串,事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。
listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
例如:
1 document.getElementById("txt").attachEvent("onclick",function(event){alert(event.keyCode);});
2. .bind() .live() .delegate()
.bind()
这是最简单的绑定方法了。JQuery扫描文档找出所有的$(‘a’)元素,并把alert函数绑定到每个元素的click事件上。
.live()
JQuery把alert函数绑定到$(document)元素上,并使用’click’和’a’作为参数。任何时候只要有事件冒泡到document节点上,它就查看该事件是否是一个click事件,以及该事件的目标元素与’a’这一CSS选择器是否匹配,如果都是的话,则执行函数。
live方法还可以被绑定到具体的元素(或context)而不是document上,像这样:
.delegate()
JQuery扫描文档查找$(‘#container’),并使用click事件和’a’这一CSS选择器作为参数把alert函数绑定到$(‘#container’)上。任何时候只要有事件冒泡到$(‘#container’)上,它就查看该事件是否是click事件,以及该事件的目标元素是否与CCS选择器相匹配。如果两种检查的结果都为真的话,它就执行函数。
可以注意到,这一过程与.live()类似,但是其把处理程序绑定到具体的元素而非document这一根上。精明的JS’er们可能会做出这样的结论,即$('a').live() == $(document).delegate('a'),是这样吗?嗯,不,不完全是。
为什么.delegate()要比.live()好用
基于几个原因,人们通常更愿意选用jQuery的delegate方法而不是live方法。考虑下面的例子:
// 或者
$(document).delegate('a', 'click', function() { blah() });
为什么.live()或.delegate()比.bind()要好:
为了把处理程序附加到可能还未存在于DOM中的DOM元素之上。因为bind是直接把处理程序绑定到各个元素上,它不能把处理程序绑定到还未存在于页面中的元素之上。
如果你运行了$(‘a’).bind(…),而后新的链接经由AJAX加入到了页面中,则你的bind处理程序对于这些新加入的链接来说是无效的。而另一方面live和delegate则是被绑定到另一个祖先节点上,因此其对于任何目前或是将来存在于该祖先元素之内的元素都是有效的。
源码分析:
(一).事件管理工具函数
事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄
1 elemData = { 2 events: { 3 'click' : [ 4 { guid: 5, type: 'click', namespace: '', data: undefined, 5 handler:function(){}//这儿的应该是绑定的函数 6 // handle: { guid: 5, prototype: {} } 7 }, 8 { ... } 9 ], 10 'keypress' : [ ... ] 11 }, 12 handle: { // DOM事件句柄 fn 13 elem: elem, 14 prototype: {} 15 } 16 }
:
1.jQuery.event.add():
参数说明:
四个参数elem、types、handler和data分别为HTMLElement、事件类型(如click)、事件响应函数、数据。此外,types 可以以空格分开传多种事件("mouseover mouseout")。handler 有时会是一个对象(实现live时)。data 最后会挂在扩充后的event对象上,即作为event的属性。而event会在handler作为第一个参数拿到,这样也就可以在handler拿到data了。
原理:
jQuery.event.add通过jQuery.data把事件相关的事件名和处理函数有机有序地组合起存放在 jQuery.cache中与该元素对应的空间里。我们就一个例子分析一下add的过程中:假如我们招待下面 jQuery(e1).bind("mouseover mouseout", fn0);jQuery(e1).bind("mouseover mouseout", fn1)的语句。
1.先调用jQuery._data从$.cache中取出已有的事件缓存
2.如果第一次在DOM元素上绑定该类事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口
3.将封装好的事件句柄放入缓存中 传入事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid,type,namespace,data属性,DOM事件句柄elemData.handle指向jQuery.evetn.handle,即jQuery在DOM元素上绑定时间时总是绑定同样的DOM事件句柄jQuery.event.handle。
1 //所有的事件绑定 2 add:function(elem,types,handler,data){ 3 //nodeType=> 1:elements 2:attr 3:text 8:comments 9:document 4 //为注释 或 文本时 直接返回 5 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 6 return; 7 } 8 //--------------处理参数handler 事件处理函数 可以是函数 也可以是包含函数的对象,当然包含值可以是其他值-------------------------- 9 //去除事件句柄的特殊情况 如果事件处理函数是false,则用retrunFalse代替false 10 if ( handler === false ) { 11 handler = returnFalse;//returnFalse 取消事件的默认行为 12 } else if ( !handler ) {//undefined null '' 三种类型,直接返回,不执行后面的代码 13 return; 14 } 15 16 var handleObjIn, handleObj; 17 18 //如果handler有handler属性,说明handler是已经封装过的handler对象 19 if ( handler.handler ) { 20 handleObjIn = handler;//handleObj是DOM事件句柄对象 21 handler = handleObjIn.handler;//handler始终是个函数 22 } 23 //如果没有id,为handler分配一个唯一的id 24 if ( !handler.guid ) { 25 handler.guid = jQuery.guid++; 26 } 27 //--------------------------获取对应对象上的$.cache数据 用于操作----------------------------------------- 28 //初始化事件结构 返回elem上的数据 通过._data()获取elem上面的数据信息 29 var elemData = jQuery._data( elem ); 30 if ( !elemData ) { 31 return; 32 } 33 34 var events = elemData.events,//事件类型和事件句柄都存储在属性events中 35 eventHandle = elemData.handle;//属性handle存放的执行这些事件句柄的DOM事件句柄 36 37 if ( !events ) { 38 //初始化一个存放事件的对象, 事件名为key 事件函数value 39 elemData.events = events = {}; 40 } 41 if ( !eventHandle ) {//初始化一个执行事件函数的函数 42 elemData.handle = eventHandle = function( e ) { 43 // Discard the second event of a jQuery.event.trigger() and 44 // when an event is called after a page has unloaded 45 // jQuery未定义 对象不存在 没触发 调用jQuery.event.handle方法 绑定对象的参数 46 return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? 47 jQuery.event.handle.apply( eventHandle.elem, arguments ) : 48 undefined; 49 }; 50 } 51 //将elem作为eventHandle的属性存储,用来避免IE中非本地事件的内存泄露 52 eventHandle.elem = elem; 53 54 // jQuery(...).bind("mouseover mouseout", fn); 55 types = types.split(" ");//同时绑定多个事件可以分割开 56 var type, i = 0, namespaces; 57 58 //---------------------------循环处理事件------------------------------------------------------- 59 while ( (type = types[ i++ ]) ) { 60 handleObj = handleObjIn ? 61 jQuery.extend({}, handleObjIn) : 62 { handler: handler, data: data };//创建事件句柄对象,后边还会添加属性:guid namespace type 63 64 //字符串中有封号,说明有命名空间 65 if ( type.indexOf(".") > -1 ) { 66 namespaces = type.split("."); 67 type = namespaces.shift();//取第一个元素 68 //余下部分作为命名空间,存储到namespace中 69 handleObj.namespace = namespaces.slice(0).sort().join("."); 70 } else { 71 //没有命名空间 72 namespaces = []; 73 handleObj.namespace = ""; 74 } 75 handleObj.type = type;//事件类型 76 if ( !handleObj.guid ) { 77 handleObj.guid = handler.guid;//事件对象唯一ID 78 } 79 //此时handleObj才是完整的事件句柄 80 81 // 获取现在的事件绑定函数列表 Get the current list of functions bound to this event 82 var handlers = events[ type ],//事件句柄队列,取出已经存在的函数数组 83 special = jQuery.event.special[ type ] || {};//特殊处理的事件 84 85 if ( !handlers ) {//如果没有绑定事件,则为对象绑定事件 86 //初始化一个数组 87 handlers = events[ type ] = []; 88 89 //---------------------------------------绑定事件------------------------- 90 //如果特殊事件没有setup属性 或 setup返回false 则用浏览器原生绑定的事件接口 91 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 92 //给元素绑定全局事件句柄 target.addEventListener(type, listener, useCapture); 93 //type:事件类型 ie下加 on 其他不加, listener:事件触发的函数句柄 useCapture:是否捕捉 94 if ( elem.addEventListener ) { 95 //默认起泡阶段捕获 96 elem.addEventListener( type, eventHandle, false ); 97 } else if ( elem.attachEvent ) { 98 //IE中没有2级DOM事件模型具有的事件捕捉的概念,只有起泡阶段 无需 true Or false 99 elem.attachEvent( "on" + type, eventHandle ); 100 } 101 } 102 } 103 104 if ( special.add ) { 105 special.add.call( elem, handleObj ); 106 107 if ( !handleObj.handler.guid ) {//始终保持事件句柄有唯一id 108 handleObj.handler.guid = handler.guid; 109 } 110 } 111 112 // Add the function to the element's handler list 113 //将句柄对象handleObj 加入到句柄数组handler列表中 114 //handleObj包含以下属性:data 唯一guid 命名空间 namespace 事件类型type handler函数 115 handlers.push( handleObj ); 116 117 //记录已经使用的事件 118 jQuery.event.global[ type ] = true; 119 } 120 // Nullify elem to prevent memory leaks in IE 121 //将elem置为空,避免IE内存泄露 122 elem = null; 123 },
2.jQuery.event.remove():删除事件句柄,所有的删除事件句柄最后都是通过jQuery.event.remove()实现的:
elem 为HTMLElement
types 为String类型,事件名称如'click'或'mouseover mouseout'
handler 为Function类型,事件回调函数
pos 为Number类型,指定数组位置
1.先调用jQuery._data从缓存$.cache中去除elem对应的所有数组.
2.如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有命名空间匹配的事件句柄 就是删除对象
3.如果是多个事件,啧分割后遍历
4.如果没有指定删除那个事件句柄,则删除事件类型对应的全部句柄 或者命名空间匹配的全部句柄
5.如果指定了删除某个事件句柄 就删除
6.所有的时间句柄删除 都直接在事件句柄数组:jQuery._data(elem).events[type]上调用splice操作
7.最后检查事件句柄数组的长度,如果是0,或为1 但要删除,则移除绑定在elem上的DOM事件
8.最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中一走elem的内部数据
pos 删除的事件开始个数 handler的属性guid唯一标示某个时间对象,移除时通过比较guid是否相等。
1 remove: function( elem, types, handler, pos ) { 2 //与添加时相同,或略掉text和comment 3 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 4 return; 5 } 6 if ( handler === false ) {//这里是为了保证handler始终是一个函数 7 handler = returnFalse; 8 } 9 10 var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, 11 //如果在缓存cache中有数据 则取出 否则elemData为undefine 12 //应为jQuery._data总是返回一个对象,因此要先判断缓存cache中是否有数组 13 elemData = jQuery.hasData( elem ) && jQuery._data( elem ), 14 //取出事件句柄数组 15 events = elemData && elemData.events;//events = elemData 并且elemData需要有events属性 16 17 if ( !elemData || !events ) {//未绑定,或略本次调用 18 return; 19 } 20 //真的事件对象,之前的是事件对象里又是对象 21 if ( types && types.type ) {//types是一个事件对象 type是事件类型 22 handler = types.handler; 23 types = types.type;//types是types.type的集合 即事件类型的数组 24 } 25 26 //如果types为false 删除所有事件句柄 27 //如果types是字符串且以(.)开头 则移除types命名空间下的指定事件(type+types) 28 if ( !types || typeof types === "string" && types.charAt(0) === "." ) { 29 types = types || "";//没有 或者字符串 或者 首字母为 . 30 for ( type in events ) {//循环遍历events,删除该事件拥有的所有事件 31 jQuery.event.remove( elem, type + types ); 32 } 33 return; 34 } 35 36 // jQuery(...).unbind("mouseover mouseout", fn); 37 types = types.split(" "); 38 39 while ( (type = types[ i++ ]) ) {//循环删除事件句柄 40 origType = type; 41 handleObj = null; 42 //all为true表示事件,false表示命名空间 43 all = type.indexOf(".") < 0;//没有点号 表示移除type命名空间下的全部事件对象 44 namespaces = []; 45 46 if ( !all ) {//类型有点 号 47 // Namespaced event handlers 48 namespaces = type.split(".");//命名空间是数组 分割type后的数组 49 type = namespaces.shift();//取出第一个作为类型 其他作为命名空间 50 //动态创建正则, 正则还没学啊 凄惨! 51 namespace = new RegExp("(^|\\.)" + 52 jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); 53 } 54 55 eventType = events[ type ];//取出type对应的事件对象为数组 56 if ( !eventType ) { 57 continue; 58 } 59 //没指定移除哪个事件对象,handler事件句柄对象,带有guid属性的函数,因此可以通过guid找到对应的时间对象 60 if ( !handler ) { 61 //遍历事件对象数组,eventType的长度是可变的,因此不能提前指定 62 for ( j = 0; j < eventType.length; j++ ) { 63 handleObj = eventType[ j ]; 64 //事件对象或命名空间 65 if ( all || namespace.test( handleObj.namespace ) ) { 66 //handleObj.handler有guid属性,迭代依然可以匹配到handleObj 67 jQuery.event.remove( elem, origType, handleObj.handler, j ); 68 eventType.splice( j--, 1 );//在这里循环遍历一个数组 69 } 70 } 71 //如果没指定,就删除全部 72 //eventType = jQuery._data(elem).events[type]可以直接操作 $.cache的直接操作进行维护,直接操作事件句柄数组 73 continue; 74 } 75 //是特殊 就特殊处理 76 special = jQuery.event.special[ type ] || {}; 77 //没指定从0开始, 78 for ( j = pos || 0; j < eventType.length; j++ ) { 79 handleObj = eventType[ j ];//单个事件 80 //比较事件函数的guid和事件对象的guid 81 if ( handler.guid === handleObj.guid ) { 82 // remove the given handler for the given type 83 if ( all || namespace.test( handleObj.namespace ) ) { 84 if ( pos == null ) {//没pos直接可以删除 85 eventType.splice( j--, 1 );//删除操作 86 } 87 //如果有特例指定的remove方法,就删除 live中有add 和 remove方法 88 if ( special.remove ) { 89 special.remove.call( elem, handleObj ); 90 } 91 } 92 // ??? 因为guid是全局唯一的,所以匹配到guid对应的事件就可以退出了 93 if ( pos != null ) {// 如果pos不为null,说明是要删除指定的事件对象,任务完成,退出 94 break; 95 } 96 } 97 } 98 99 //type对应的事件对象为空数组,或只有一个 ,则移除绑定在elem上浏览器原生事件 100 if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { 101 //验证特殊方式,如果不存在或者返回false 则调用普通方式 102 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { 103 jQuery.removeEvent( elem, type, elemData.handle ); 104 } 105 106 ret = null;//?? 107 delete events[ type ];//删除类型 108 } 109 } 110 111 //如果不在用了 就删除 112 if ( jQuery.isEmptyObject( events ) ) {//从jQuery.cache中移除elem数据最后elemData.events 113 var handle = elemData.handle; 114 if ( handle ) { 115 handle.elem = null;//置空 引用完HTML元素后一定要置空 116 } 117 118 delete elemData.events;//删除存储事件句柄的对象 119 delete elemData.handle;//删除DOM事件句柄 120 121 //事件句柄 和对象都遭删除了,难道还要删除上面的数据吗??? 122 if ( jQuery.isEmptyObject( elemData ) ) { 123 jQuery.removeData( elem, undefined, true ); 124 } 125 } 126 },
(二).绑定与解绑定函数
.bind( eventType, [eventData], handler(eventObject) ) 在匹配的元素上绑定指定类型的事件处理函数
.bind( eventType, [eventData], preventBubble ) 第三个参数为false,则阻止浏览器默认行为并停止事件冒泡,默认true
.bind( events ) 绑定多个事件,events的 键为事件类型,值为对应的事件处理函数
.one( eventType, [eventData], handler(eventObject) ) 在匹配的元素上绑定指定类型的事件处理函数,这个函数最多执行一次
其实是改写了事件处理函数,函数执行时先解绑定,再执行。
1.bind()与one():
1 //在匹配的元素上绑定指定类型的事件处理函数 2 jQuery.each(["bind", "one"], function( i, name ) { 3 //为jQuery对象扩展bind one 方法 4 jQuery.fn[name] = function(type,data,fn){ 5 var handler; 6 //绑定多个事件 key为事件类型 type[key]为事件处理函数 7 if(typeof type === "object"){ 8 for(var key in type){ 9 this[name](key,data,type[key],fn); 10 } 11 return this; 12 } 13 14 /* 如果没有传入data 或data为false 15 如果两个参数,则认为忽略了data : type fn 结果 type undefined fn 16 如果data为false 则认为是type false fn 结果是:type undefined false 17 */ 18 if(arguments.length === 2 || data === false){ 19 fn = data; 20 data = undefined; 21 } 22 //one句柄和其他句柄分开处理 23 if(name === "one"){//只执行一次 24 handler = function(event){//创建一个新的时间处理函数句柄 25 jQuery(this).unbind(event,handler);//先删除事件 26 return fn.apply(this,arguments);//执行传入的时间处理函数 this为DOM元素 27 }; 28 handler.guid = fn.guid || jQuery.guid++ ;//同步guid 与原来的guid一样 29 }else{ 30 handler.fn; 31 } 32 33 if(type === "unload" && name !== "one"){//如果是unload事件 只执行一次 34 this.one(type,data,fn);//执行one方法 35 }else{ 36 for(var i =0,l = this.length;i<l;i++){ 37 jQuery.evetn.add(this[i],type,handler,data);//遍历匹配元素,并绑定时间处理函数 38 } 39 } 40 41 return this;//这样就可以链式调用了 42 }; 43 });
2.unbind():
1 //解绑定:删除一个之前附加的事件句柄 jQuery事件之鞥在起泡阶段捕获,不需要定义控制捕获阶段的参数 2 unbind: function( type, fn ) { 3 //一次删除多个事件句柄 4 if(typeof type === "object" && !type.preventDefault){ 5 for(var key in type){//循环调用 6 this.unbind(key,type[key]); 7 } 8 }else{ 9 for(var i =0 ,l = this.length;i<l;i++){ 10 //最后的调用都落到了remove上面了 11 jQuery.event.remove(this[i],type,fn);//调用remove删除句柄 12 } 13 } 14 },
2.live()与die():
live 在匹配当前选择器的元素上绑定一个时间处理函数 包括已经存在的 和未来添加的 即任何添加的元素只要匹配当前选择器就会被绑定处理函数
die 移除live附加的一个活全部事件处理函数
.live(eventType,handler) 在匹配当前选择器的元素上绑定一个指定类型的时间处理函数
.live(eventType,eventData,handler) eventData可以传递数据给时间处理函数
.live(events) 绑定多个事件
.die() 删除之前通过.live()绑定的全部事件句柄
.die(eventType[,handler]) 删除一个或一类之前通过 .live()绑定的事件句柄
.die(eventTypes) eventTypes是Map对象 删除多类事件句柄
.live()方法能对尚未添加到DOM文档中的元素生效。
.live()方法永远不会将事件绑定到匹配的元素上,而是将事件绑定到祖先元素(document或context),由该祖先元素代理子元素的事件。 *
以 $('.clickme').live('click', function() { … } ) 为例,当在新添加的元素上点击时,执行过程如下:
1. click事件被生成,并传递给<div>,待<div>处理
2. 因为<div>没有直接绑定click事件,因此事件沿着DOM树进行冒泡传递
3. 事件冒泡传递直到到达DOM树的根节点,在根节点上绑定了.live()指定的原始事件处理函数(在1.4以后的版本中,.live()绑定事件到上下文context,以提升性能)
4. 执行.live()指定的事件处理函数
5. 原始事件处理函数检查event的target属性以确定是否继续执行,检查的方式是 $(event.target).closest(".clickme")
6. 如果匹配,则原始事件处理函数执行,上下位被设置为找到的元素.
1 jQuery.each(["live", "die"], function( i, name ) { 2 jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { 3 var type,i = 0 ,match,namespaces,preType, 4 // 很巧妙,如果没有origSelector,那么采用当前jQuery对象的选择器,、 5 // 可是,如果origSelector为true,后边的处理对象就变成了以origSelector为选择器的jQuery对象 6 selector = origSelcetor || this.selector,//选择器表达式 7 // 这里也是,如果没有origSelector,那么上下文就变成当前jQuery对象的上下文 8 // 可是,如果origSelector为true(类型转换),当前jQuery对象就变成上下文了 9 // 就是在这里,通过一个内部变量,改变了选择器表达式和上下分,区分开了live/die和delegate/undelegate,用同样的代码实现了两种接口 10 context = origSelector ? this : jQuery(this.context);//上下文 11 //一次绑定或删除多个事件 12 if(typeof types === "object" && !types.preventDefault){ 13 for(var key in types){ 14 context[name](key,data,types[key],selector); 15 } 16 return this; 17 } 18 // 如果是die,移除origSelector匹配元素上的所有通过live绑定的事件 19 // 没有指定事件类型 + origSelector + origSelector是CSS选择器 20 if ( name === "die" && !types && 21 origSelector && origSelector.charAt(0) === "." ) { 22 23 context.unbind( origSelector ); // 事实上bind/unbind是基础,add/remove/tigger/handle是所有事件的基础 24 25 return this; 26 } 27 // 修正参数(如果data为false或没有传入data参数) 28 // 完整:types, data, fn, origSelector 29 // types, false, fn, origSelector 30 // 或 types, fn, origSelector 31 if ( data === false || jQuery.isFunction( data ) ) { 32 fn = data || returnFalse; 33 data = undefined; 34 } 35 types = (types || "").split(" "); // 多个事件用空格隔开 36 37 while ( (type = types[ i++ ]) != null ) { // 遍历事件类型数组 38 match = rnamespaces.exec( type ); // 取到第一个.后的命名空间 39 namespaces = ""; 40 41 if ( match ) { // 如果有命名空间,即事件类型后还有. 42 namespaces = match[0]; // .+命名空间 43 type = type.replace( rnamespaces, "" ); // 过滤.+命名空间,初学者会尝试用indexOf查找.的位置,然后截取 44 } 45 46 if ( type === "hover" ) { // 将hover分解为mouseenter mouseleave,继续遍历 47 types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); 48 continue; 49 } 50 51 preType = type; // 52 53 if ( liveMap[ type ] ) { // 修正事件名名,修正其实不准确,比如遇到focus,绑定了两个:focus focusin,不会造成两次事件响应么? 54 types.push( liveMap[ type ] + namespaces ); // 将修正后的事件类型入队,因为用了while循环,所有不必担心遍历动态数组的问题 55 type = type + namespaces; // 再恢复type,包含了命名空间,这不是蛋疼么,把命名空间去掉只为了检测hover和是否需要修正? 56 57 } else { 58 type = (liveMap[ type ] || type) + namespaces; // 修正事件名,这里可以直接写成:type + namespaces 59 // 这行的逻辑有点问题,已经知道liveMap[ type ]是false了,这个地方的代码有些重复 60 } 61 /** 62 * 上边的if-else改写为: 63 * if ( liveMap[ type ] ) types.push( liveMap[ type ] + namespaces ) 64 * type = type + namespaces 65 */ 66 67 // 这里开始live的特殊处理,虽说live/die的逻辑比较接近,再加上delegate/undelegate,复用的粒度小但是很精髓 68 // 同时为了减少接口,并没有将公共部分提取出来,总的来说,live/die这段代码出来的还是刚刚好 69 if ( name === "live" ) { 70 // bind live handler 71 for ( var j = 0, l = context.length; j < l; j++ ) { 72 // 绑定到上下文,事件类型经过liveConvert后变为 live.type.selector 73 // 前边做了那么多铺垫,这行才是关键! 74 // add: function( elem, types, handler, data ) { 75 // context[j] 如果有origSelector则是当前jQuery对象,如果没有则是当前jQuery对象的上下文 76 // live事件的格式比较特殊,应该trigger里会有live的特殊处理,live的处理在特例special里 77 jQuery.event.add( context[j], "live." + liveConvert( type, selector ), 78 { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); 79 } 80 81 } else { 82 // unbind live handler 83 // 删除live绑定的事件句柄 84 context.unbind( "live." + liveConvert( type, selector ), fn ); 85 } 86 } 87 88 return this; 89 }; 90 });
3.delegate()与undelegate():
1 //事件代理,调用live方法实现 2 delegate: function( selector, types, data, fn ) { 3 //因为传递了参数selector live时的上下文变为this 不是this.context 4 return this.live(types,data,fn,selector); 5 }, 6 //删除事件代理,调用unbind或die实现 7 undelegate: function( selector, types, fn ) { 8 if(arguments.length === 0){ 9 return this.unbind("live");//在当前jQuery对象上直接unbind 10 }else{ 11 return this.die(types,null,fn,selector); 12 } 13 },
4.handle() fix():
handle 是一个函数。jQuery为每一个DOM元素绑定的任意类型事件的响应函数都是此函数,并不是直接将用户传入的函数绑定为响应函数。该函数会在执行时调用 一个公共方法jQuery.event.handle,通过apply更改this的指向,此函数还有一个静态属性elem,也是指向DOM元素自己。
当用户触发事件后,jQuery.event.handle首先会调用jQuery.event.fix对Event对象做兼容处理,之后根据Event.type从jQuery.cache中获取用户绑定时传入的响应函数,逐个运行。
1 handle:function(event){ 2 event = jQuery.event.fix( event || window.event );//使用jQuery.Event封装原始事件,并修正属性 3 // Snapshot the handlers list since a called handler may add/remove events. 4 var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), 5 run_all = !event.exclusive && !event.namespace, 6 args = Array.prototype.slice.call( arguments, 0 ); 7 // Use the fix-ed Event rather than the (read-only) native event 8 args[0] = event; 9 event.currentTarget = this; 10 11 for ( var j = 0, l = handlers.length; j < l; j++ ) { 12 var handleObj = handlers[ j ]; 13 14 // Triggered event must 1) be non-exclusive and have no namespace, or 15 // 2) have namespace(s) a subset or equal to those in the bound event. 16 if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { 17 // Pass in a reference to the handler function itself 18 // So that we can later remove it 19 event.handler = handleObj.handler; 20 event.data = handleObj.data; 21 event.handleObj = handleObj; 22 23 var ret = handleObj.handler.apply( this, args ); 24 if ( ret !== undefined ) { 25 event.result = ret; 26 if ( ret === false ) { 27 event.preventDefault(); 28 event.stopPropagation(); 29 } 30 } 31 if ( event.isImmediatePropagationStopped() ) { 32 break; 33 } 34 } 35 } 36 return event.result; 37 },
jQuery.event.fix对Event对象做兼容处理:
它主要做了以下工作
1,event = jQuery.Event( originalEvent ); 该句创建了一个jQuery.Event类的实例对象,该对象修复及扩充上面刚刚提到了。
2, 一个循环将原生事件对象的所有属性拷贝给 1 中的event对象。
1 for ( var i = this.props.length, prop; i; ) { 2 prop = this.props[ --i ]; 3 event[ prop ] = originalEvent[ prop ]; 4 }
3, 统一事件源对象为 target 。
4, 统一事件相关对象为 relativeTarget 。
5, 扩充了pageX , pageY ,这两个属性首次在Firefox中引入的。不支持该属性的浏览器使用clientX/Y计算得到。
6, 扩充了 which ,使用它获取键盘按键值(keyCode)。这个属性也是在Firefox引入的。
7, 修复了metaKey。
8, 扩充了which,使用它获取鼠标按键值
1 fix:function(event){ 2 //事件有唯一ID 返回事件 3 if ( event[ jQuery.expando ] ) { 4 return event; 5 } 6 7 // store a copy of the original event object 8 // and "clone" to set read-only properties 9 var originalEvent = event;//备份一个事件对象 10 event = jQuery.Event( originalEvent ); 11 12 for ( var i = this.props.length, prop; i; ) { 13 prop = this.props[ --i ]; 14 event[ prop ] = originalEvent[ prop ]; 15 } 16 17 // Fix target property, if necessary 18 if ( !event.target ) { 19 //目标对象 20 event.target = event.srcElement || document; 21 } 22 23 if ( event.target.nodeType === 3 ) {// check if target is a textnode (safari) 24 event.target = event.target.parentNode; 25 } 26 27 if ( !event.relatedTarget && event.fromElement ) {// Add relatedTarget, if necessary 28 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 29 } 30 31 if ( event.pageX == null && event.clientX != null ) {// Calculate pageX/Y if missing and clientX/Y available 32 var eventDocument = event.target.ownerDocument || document, 33 doc = eventDocument.documentElement, 34 body = eventDocument.body; 35 36 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 37 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 38 } 39 // Add which for key events 40 if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { 41 event.which = event.charCode != null ? event.charCode : event.keyCode; 42 } 43 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) 44 if ( !event.metaKey && event.ctrlKey ) { 45 event.metaKey = event.ctrlKey; 46 } 47 // Add which for click: 1 === left; 2 === middle; 3 === right 48 // Note: button is not normalized, so don't use it 49 if ( !event.which && event.button !== undefined ) { 50 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); 51 } 52 53 return event; 54 },
jQuery.event.trigger():
.trigger 执行事件hanlder/执行冒泡/执行默认行为 不是true
.triggerHandler 执行事件handler/不冒泡/不执行默认行为 有参数true
可以看出,两者都调用jQuery.event.trigger。调用时一个没有传true,一个传了。传了true的triggerHander就表示仅执行事件handler.
此外还需注意一点区别:.trigger是对jQuery对象集合的操作,而.triggerHandler仅操作jQuery对象的第一个元素
1 trigger:function(event,data,elem,onlyHandler){ 2 // Event object or event type 3 var type = event.type || event,//事件对象或事件类型 4 namespaces = [], 5 exclusive; 6 //比如.trigger('click!'),为了处理非命名空间类型的。 7 //变量exclusive挂载事件对象上后在jQuery.event.handle内使用 8 /* 9 function fn1() { 10 console.log(1) 11 } 12 function fn2() { 13 console.log(2) 14 } 15 $(document).bind('click.a', fn1); 16 $(document).bind('click', fn2); 17 $(document).trigger('click!'); // 2 18 19 为document添加了两个点击事件,一个是具有命名空间的"click.a",一个则没有"click"。使用trigger时参数click后加个叹号"!"。 20 从输出结果为2可以看出不触发命名空间的事件。总结一下: 21 .trigger('click') 触发所有的点击事件 22 .trigger('click.a') 仅触发“click.a” 的点击事件 23 .trigger('click!') 触发非命名空间的点击事件 24 */ 25 //没有点号的帮点事件,都可以执行 26 if ( type.indexOf("!") >= 0 ) {//type里面有! 27 // Exclusive events trigger only for the exact event (no namespaces) 28 type = type.slice(0, -1); 29 exclusive = true; 30 } 31 //对命名空间的处理 32 if ( type.indexOf(".") >= 0 ) { 33 // Namespaced trigger; create a regexp to match event type in handle() 34 namespaces = type.split("."); 35 type = namespaces.shift(); 36 namespaces.sort(); 37 } 38 39 //特殊事件如getData 或对于已经触发过的事件直接返回 40 if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { 41 return; 42 } 43 //1.event本身就是jQuery.Event类的实例 44 //2.event是个普通的js对象 45 //3.event是个字符串 如click 46 event = typeof event === "object" ? 47 // jQuery.Event object 48 event[ jQuery.expando ] ? event : 49 // Object literal 50 new jQuery.Event( type, event ) : 51 // Just the event type (string) 52 new jQuery.Event( type ); 53 //exclusive/namespace/namespace_re挂到了event上了,在jQuery.event.handle中可以用到(事件命名空间)。 54 event.type = type; 55 event.exclusive = exclusive; 56 event.namespace = namespaces.join("."); 57 event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); 58 59 //onlyHandlers 只在 .triggerHandler用到了,即不触发元素的默认行为,且停止冒泡。 60 if ( onlyHandlers || !elem ) {//onlyHandlers :true 61 event.preventDefault(); 62 event.stopPropagation(); 63 } 64 65 //这里是递归调用 如果没有elem元素 从jQuery.cache里取 66 if ( !elem ) { 67 // TODO: Stop taunting the data cache; remove global events and always attach to document 68 jQuery.each( jQuery.cache, function() { 69 70 var internalKey = jQuery.expando, 71 internalCache = this[ internalKey ];//事件id 72 if ( internalCache && internalCache.events && internalCache.events[ type ] ) { 73 jQuery.event.trigger( event, data, internalCache.handle.elem ); 74 } 75 }); 76 return; 77 } 78 79 //text comment 直接返回 80 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 81 return; 82 } 83 84 // Clean up the event in case it is being reused 85 event.result = undefined; 86 event.target = elem; 87 88 //先将参数data放入数组,event对象放入数组第一个位置 89 data = data ? jQuery.makeArray( data ) : []; 90 data.unshift( event ); 91 92 var cur = elem, 93 // IE doesn't like method names with a colon (#3533, #8272) 94 ontype = type.indexOf(":") < 0 ? "on" + type : ""; 95 96 /* 1,取handle 97 2,执行 98 3,执行通过onXXX方式添加的事件(如onclick="fun()") 99 4,取父元素 100 while循环不断重复这四步以模拟事件冒泡。直到window对象。*/ 101 do { 102 var handle = jQuery._data( cur, "handle" ); 103 104 event.currentTarget = cur; 105 if ( handle ) { 106 handle.apply( cur, data ); 107 } 108 109 // Trigger an inline bound script 110 if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { 111 event.result = false; 112 event.preventDefault(); 113 } 114 115 // Bubble up to document, then to window 116 cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; 117 } while ( cur && !event.isPropagationStopped() ); 118 119 // If nobody prevented the default action, do it now 120 if ( !event.isDefaultPrevented() ) { 121 var old, 122 special = jQuery.event.special[ type ] || {}; 123 124 if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && 125 !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { 126 127 // Call a native DOM method on the target with the same name name as the event. 128 // Can't use an .isFunction)() check here because IE6/7 fails that test. 129 // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. 130 try { 131 if ( ontype && elem[ type ] ) { 132 // Don't re-trigger an onFOO event when we call its FOO() method 133 old = elem[ ontype ]; 134 135 if ( old ) { 136 elem[ ontype ] = null; 137 } 138 139 jQuery.event.triggered = type; 140 elem[ type ](); 141 } 142 } catch ( ieError ) {} 143 144 if ( old ) { 145 elem[ ontype ] = old; 146 } 147 148 jQuery.event.triggered = undefined; 149 } 150 } 151 152 return event.result; 153 },
1 //执行事件处理函数和默认行为 2 trigger: function( type, data ) { 3 return this.each(function() { 4 jQuery.event.trigger( type, data, this ); 5 }); 6 }, 7 //执行事件处理函数 不执行默认行为 只触发匹配的第一个元素 不反悔jQuery对象 8 triggerHandler: function( type, data ) { 9 if ( this[0] ) { 10 return jQuery.event.trigger( type, data, this[0], true ); 11 } 12 },
posted on 2013-04-23 15:28 color_story 阅读(449) 评论(0) 编辑 收藏 举报