说起jQuery的事件,不得不提一下Dean Edwards大神 addEvent库,很多流行的类库的基本思想从他那儿借来的

jQuery的事件处理机制吸取了JavaScript专家Dean Edwards编写的事件处理函数的精华,使得jQuery处理事件绑定的时候相当的可靠。

在预留退路(graceful degradation),循序渐进以及非入侵式编程思想方面,jQuery也做的非常不错

事件的流程图

总的来说对于JQuery的事件绑定

在绑定的时候做了包装处理

在执行的时候有过滤器处理


.on( events [, selector ] [, data ], handler(eventObject) )

events:事件名

selector : 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素

data :当一个事件被触发时,要传递给事件处理函数的

handler:事件被触发时,执行的函数

例如:

var body = $('body')
body.on('click','p',function(){
console.log(this)
})

用on方法给body上绑定一个click事件,冒泡到p元素的时候才出发回调函数

这里大家需要明确一点:每次在body上点击其实都会触发事件,但是只目标为p元素的情况下才会触发回调handler

通过源码不难发现,on方法实质只完成一些参数调整的工作,而实际负责事件绑定的是其内部jQuery.event.add方法

jQuery.onon: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type; // Types can be a map of types/handlers
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for ( type in types ) {
this.on( type, selector, data, types[ type ], one );
}
return this;
} if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
return this;
} if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});

针对事件处理,我们可以拆分2部分:

一个事件预绑定期

一个事件执行期

本章着重讲解事件的预绑定的时候做了那些处理,为什么要这样处理?

事件底层的绑定接口无非就是用addEventListener处理的,所以我们直接定位到addEventListener下面

jQuery.event.add 中有

elem: 目标元素

type: 事件类型,如’click’

eventHandle: 事件句柄,也就是事件回调处理的内容了

false: 冒泡

现在我们把之前的案例给套一下看看

var body = document.getElementsByTagName('body')

var eventHandle = function(){
console.log(this)
} body .addEventListener( ‘click’, eventHandle, false );

明显有问题,每次在body上都触发了回调,少了个p元素的处理,当然这样的效果也无法处理


eventHandle源码

回到内部绑定的事件句柄eventHandle ,可想而知eventHandle不仅仅只是只是充当一个回调函数的角色,而是一个实现了EventListener接口的对象

if ( !(eventHandle = elemData.handle) ) {
eventHandle = elemData.handle = function( e ) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}

可见在eventHandle中并没有直接处理回调函数,而是映射到jQuery.event.dispatch分派事件处理函数了

仅仅只是传入eventHandle.elem,arguments , 就是body元素 与事件对象

那么这里有个问题,事件回调的句柄并没有传递过去,后面的代码如何关联?

本章的一些地方可能要结合后面的dispatch处理才能理清,但是我们还是先看看做了那些处理


on内部的实现机制

我们开从头来理清下jQuery.event.add代码结构,适当的跳过这个环节中不能理解的代码,具体遇到在提出

之前就提到过jQuery从1.2.3版本引入数据缓存系统,贯穿内部,为整个体系服务,事件体系也引入了这个缓存机制

所以jQuery并没有将事件处理函数直接绑定到DOM元素上,而是通过$.data存储在缓存$.cahce上

第一步:获取数据缓存

//获取数据缓存
elemData = data_priv.get( elem );

在$.cahce缓存中获取存储的事件句柄对象,如果没就新建elemData

第二步:创建编号

if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}

为每一个事件的句柄给一个标示,添加ID的目的是 用来寻找或者删除handler,因为这个东东是缓存在缓存对象上的,没有直接跟元素节点发生关联

第三步:分解事件名与句柄

if ( !(events = elemData.events) ) {
events = elemData.events= {};
} if ( !(eventHandle = elemData.handle) ) {
eventHandle = elemData.handle = function( e ) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
eventHandle.elem = elem;
}

events,eventHandle 都是elemData缓存对象内部的,可见

在elemData中有两个重要的属性,

一个是events,是jQuery内部维护的事件列队

一个是handle,是实际绑定到elem中的事件处理函数

之后的代码无非就是对这2个对象的筛选,分组,填充了

第四步: 填充事件名与事件句柄

// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
// 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组
// core_rnotwhite:/\S+/g
types = ( types || "" ).match( core_rnotwhite ) || [""];
// 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"]
// 事件的个数
t = types.length; while ( t-- ) {
// 尝试取出事件的命名空间
// 如"mouseover.a.b" → ["mouseover.a.b", "mouseover", "a.b"]
tmp = rtypenamespace.exec( types[t] ) || [];
// 取出事件类型,如mouseover
type = origType = tmp[1];
// 取出事件命名空间,如a.b,并根据"."分隔成数组
namespaces = ( tmp[2] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
} // If event changes its type, use the special event handlers for the changed type
// 事件是否会改变当前状态,如果会则使用特殊事件
special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type
// 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type
type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type
// type状态发生改变,重新定义特殊事件
special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers
// 这里把handleObj叫做事件处理对象,扩展一些来着handleObjIn的属性
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 ); // Init the event handler queue if we're the first
// 初始化事件处理列队,如果是第一次使用,将执行语句
if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false
// 如果获取特殊事件监听方法失败,则使用addEventListener进行添加事件
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
}
}
} // 特殊事件使用add处理
if ( special.add ) {
special.add.call( elem, handleObj );
// 设置事件处理函数的ID
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
} // Add to the element's handler list, delegates in front
// 将事件处理对象推入处理列表,姑且定义为事件处理对象包
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
} // Keep track of which events have ever been used, for event optimization
// 表示事件曾经使用过,用于事件优化
jQuery.event.global[ type ] = true;
} // Nullify elem to prevent memory leaks in IE
// 设置为null避免IE中循环引用导致的内存泄露
elem = null;
},

这段比较长了分解下,最终的目的就是为填充events,eventHandle

涉及

多事件处理

如果是多事件分组的情况jQuery(...).bind("mouseover mouseout", fn);

事件可能是通过空格键分隔的字符串,所以将其变成字符串数组

增加命名空间处理

事件名称可以添加指定的event namespaces(命名空间) 来简化删除或触发事件。例如,"click.myPlugin.simple"为 click 事件同时定义了两个命名空间 myPlugin 和 simple。通过上述方法绑定的 click 事件处理,可以用.off("click.myPlugin").off("click.simple")删除绑定到相应元素的Click事件处理程序,而不会干扰其他绑定在该元素上的“click(点击)” 事件。命名空间类似CSS类,因为它们是不分层次的;只需要有一个名字相匹配即可。以下划线开头的名字空间是供 jQuery 使用的。

引入jQuery的Special Event机制

什么时候要用到自定义函数?有些浏览器并不兼容某类型的事件,如IE6~8不支持hashchange事件,你无法通过jQuery(window).bind('hashchange', callback)来绑定这个事件,这个时候你就可以通过jQuery自定义事件接口来模拟这个事件,做到跨浏览器兼容。

原理

jQuery(elem).bind(type, callbakc)实际上是映射到 jQuery.event.add(elem, types, handler, data)这个方法,每一个类型的事件会初始化一次事件处理器,而传入的回调函数会以数组的方式缓存起来,当事件触发的时候处理器将依次执行这个数组。
jQuery.event.add方法在第一次初始化处理器的时候会检查是否为自定义事件,如果存在则将会把控制权限交给自定义事件的事件初始化函数,同样事件卸载的jQuery.event.remove方法在删除处理器前也会检查此。

!special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false
jQuery.removeEvent( elem, type, elemData.handle );

jQuery.event.special对象中,保存着为适配特定事件所需的变量和方法,

具体有:
delegateType / bindType (用于事件类型的调整)
setup (在某一种事件第一次绑定时调用)
add (在事件绑定时调用)
remove (在解除事件绑定时调用)
teardown (在所有事件绑定都被解除时调用)
trigger (在内部trigger事件的时候调用)
noBubble
_default
handle (在实际触发事件时调用)
preDispatch (在实际触发事件前调用)
postDispatch (在实际触发事件后调用)

在适配工作完成时,会产生一个handleObj对象,这个对象包含了所有在事件实际被触发是所需的所有参数

采用自定义事件或者浏览器接口绑定事件

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
}
}

冒泡标记

  handlers.splice( handlers.delegateCount++, 0, handleObj );

最后记得

设置为null避免IE中循环引用导致的内存泄露

elem = null;

这个元素没有直接让事件直接引用了,而是挂在到,数据缓存句柄上,很好的避免了这个IE泄露的问题

eventHandle.elem = elem;

通过整个流程,我们的数据缓存对象就填充完毕了,看看截图

events:handleObj

handle

数据缓存对象


得出总结:

在jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : 方法中没有传递回调对象

是因为回调的句柄被关联到了elemData,也就是内部数据缓存中了

不难得出jQuery的事件绑定机制:

jQuery对每一个elem中的每一种事件,只会绑定一次事件处理函数(绑定这个elemData.handle),

而这个elemData.handle实际只做一件事,就是把event丢到jQuery内部的事件分发程序

jQuery.event.dispatch.apply( eventHandle.elem, arguments );

而不同的事件绑定,具体是由jQuery内部维护的事件列队来区分(就是那个elemData.events)

在elemData中获取到events和handle之后,接下来就需要知道这次绑定的是什么事件了

画了个简单流程图

本章只是事件的前段部分,解析完elemData数据,在执行期间又会如何处理,如何巧妙的运用这些设计,下章分晓!

如果觉得有帮助,请点击下推荐,分享给更多有需要的人~

解密jQuery事件核心 - 绑定设计(一)的更多相关文章

  1. 解密jQuery事件核心 - 委托设计(二)

    第一篇 http://www.cnblogs.com/aaronjs/p/3444874.html 从上章就能得出几个信息: 事件信息都存储在数据缓存中 对于没有特殊事件特有监听方法和普通事件都用ad ...

  2. 解密jQuery事件核心 - 自定义设计(三)

    接上文http://www.cnblogs.com/aaronjs/p/3447483.html 本文重点:自定义事件 “通过事件机制,可以将类设计为独立的模块,通过事件对外通信,提高了程序的开发效率 ...

  3. 解密jQuery事件核心 - 模拟事件(四)

    前几章已经把最核心的实现都分解过了,这一章我们看看jQuery是如何实现事件模拟的 在Internet Explorer 8和更低,一些事件change 和 submit本身不冒泡,但jQuery修改 ...

  4. JQuery事件的绑定

    关于jQuery事件绑定html: <a href="#" onclick="addBtn()">addBtn</a> <div ...

  5. jquery 事件的绑定,触发和解绑

    js和jquery绑定的区别? HTML或原生js是单一对应绑定的,绑多了只留最后一个.jQuery是追加绑定的,绑多少执行多少.这个在每一本jQuery的书中都是首先提到的事情. jquery绑定与 ...

  6. jquery事件重复绑定

    本文实例分析了jQuery防止重复绑定事件的解决方法.分享给大家供大家参考,具体如下: 一.问题: 今天发现jQuery一个对象的事件可以重复绑定多次,当事件触发的时候会引起代码多遍执行. 下面是一个 ...

  7. jquery事件重复绑定的几种解决方法 (二)

    防止事件重复绑定共有4种方法: bind().unbind()方法 live().die()方法 off().on()方法 one()方法 一.bind().unbind()方法 bind();绑定事 ...

  8. jquery事件重复绑定的几种解决方法

    防止事件重复绑定共有4种方法: bind().unbind()方法 live().die()方法 off().on()方法 one()方法 一.bind().unbind()方法 bind();绑定事 ...

  9. jquery事件重复绑定解决办法

    一$.fn.live 重复绑定 解决:使用die()方法,在live()方法绑定前,将此元素上的前面被绑定的事件统统解除,然后再通过live()方法绑定新的事件. //先通过die()方法解除,再通过 ...

随机推荐

  1. csv

    csv 文件的读写:http://www.cnblogs.com/fiozhao/p/3225112.html 求取一个立方体的对角线穿个的边长为1的正方体的个数:http://www.cnblogs ...

  2. JAVA集合类型详解

    一.前言 作为java面试的常客[集合类型]是永恒的话题:在开发中,主要了解具体的使用,没有太多的去关注具体的理论说明,掌握那几种常用的集合类型貌似也就够使用了:导致这一些集合类型的理论有可能经常的忘 ...

  3. 【BZOJ】4056: [Ctsc2015]shallot

    题意 在线.可持久化地维护一条二维平面上的折线,支持查询与任意一条直线的交点个数. 点的个数和操作个数小于\(10^5\) 分析 一条折线可以用一个序列表示,可持久化序列考虑用可持久化treap. 如 ...

  4. vs2013 手动生成webservice代理类wsdl

    第一步: 第二步: 第三步: 至此wsdl代理类生成成功!

  5. share

    一:struts2简介 (1)struts1和struts2 webwork struts2 (在struts2出来之前,有两个特别流行的框架,一个叫struts1一个是web work,那个时候st ...

  6. Reading C type declarations(引用http://unixwiz.net/techtips/reading-cdecl.html)

    Even relatively new C programmers have no trouble reading simple C declarations such as int foo[5]; ...

  7. String对象方法扩展

    /** *字符串-格式化 */ String.prototype.format = function(){ var args = arguments;//获取函数传递参数数组,以便在replace回调 ...

  8. scala - multiple overloaded alternatives of method bar define default arguments

    同名同位置默认参数不能overload def bar(i:Int,s:String="a"){} def bar(i:String,s:String="b") ...

  9. NSIS 无边框移动问题总结笔记

    无边框移动 插件 WinProc WinCore.nsh [一定要有这个] 代码 ;事件 ;处理无边框移动 Function onGUICallback ${If} $MSG = ${WM_LBUTT ...

  10. RSA密钥生成与使用

    RSA密钥生成与使用 openssl生成工具链接:http://pan.baidu.com/s/1c0v3UxE 密码:uv48 1. 打开openssl密钥生成软件打开 openssl 文件夹下的  ...