本篇来介绍下关于 jQ 绑定事件的流程:
先来看下对外的api :
$().click(); $().change(); ...;
$().on(); $().one(); $().bind(); $().delegate();
以上这些绑定最终都会执行到 $().on() 方法中,
先来看下结构:
jQuery.fn.extend({
on: function( types, selector, data, fn, one ){
//....
jQuery.event.add(/*一堆参数*/);
},
one: function( types, selector, data, fn ) {
return this.on( types, selector, data, fn, 1 );
},
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
});
有人可能会奇怪了,click神马的没有列出?是的,那个小编放到了后边,
小编先告诉您,在那些快捷方法(暂且先这么称呼吧)中,也是调用了 $().on();方法
jQuery.fn.extend({
//绑定事件
//selector指的是要代理的目标元素,delegate,on,one,均有体现
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type;
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for ( type in types ) {
// { click: fn, mouseover: fn }
this.on( type, selector, data, types[ type ], one );
}
return this;
}
//以下是针对各种调用情况的参数处理
//jQ通过参数的顺序和类型的检测,以分辨各种调用情况。
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;
}
}
//如果处理方法是false,则设置为 fn:return false;
if( fn === false ){
// function returnFalse(){ return false; };
fn = returnFalse;
//如果没有传入处理函数,也就不用后续了
}else if( !fn ){
return this;
}
//如果是one的情况,则将fn封装为一个只执行一次的函数
if ( one === 1 ) {
origFn = fn;
//在事件触发时,先进行移除事件函数,然后在执行
fn = function( event ) {
jQuery().off( event );
return origFn.apply( this, arguments );
};
//设置函数的唯一标示,fn,origFn是同一个函数,所以这里要设置一个相同的标示
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
},
//...
});
可以看到在on方法的末尾,调用了jQuery.event.add,
而这是真实绑定到dom上事件的处理,来看下代码吧
jQuery.event = {
// 绑定一个或多个类型的事件监听函数
add: function( elem, types, handler, data, selector ) {
var elemData, eventHandle, events,
t, tns, type, namespaces, handleObj,
handleObjIn, quick, handlers, special;
// 文本节点 || 注释节点 || 参数残缺不全 || 元素不支持绑定属性
// jQuery._data(elem): 如果元素不支持则 return ; 支持的则 return object; 没有数据也会 return {};
if( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler ||
!(elemData = jQuery._data( elem )) ){
return;
}
// handler可以是一个自定义监听对象
// 当handler是对象时,从中取出以下这些参数,这个在后面的 cloneCopyEvent 函数中会有应用
// 这里大家可以忽略这个if,像后看
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// 为每个, 没有标示的函数, 分配一个标示
// 这个标示将会用于移除函数
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// 取出存储事件的容器,还没有则重新赋值
events = elemData.events;
if ( !events ) {
elemData.events = events = {};
}
// 取出主监听函数
eventHandle = elemData.handle;
// 没有的话,先设置一个主监听函数
if ( !eventHandle ) {
//jQ的事件系统只会为元素分配一个主监听函数,
//并且所有类型的事件在绑定时,只会绑定这个函数
elemData.handle = eventHandle = function( e ) {
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
// 将dom元素绑定到事件的监听函数上
eventHandle.elem = elem;
}
//将"hover"拆分成"mouseenter mouseleave".split;
types = jQuery.trim( hoverHack(types) ).split( " " );
for ( t = 0; t < types.length; t++ ) {
// 'click.namespace','click','namespace'
tns = rtypenamespace.exec( types[t] ) || [];
type = tns[1];
// 命名空间可以指定多个, 以 . 分开
namespaces = ( tns[2] || "" ).split( "." ).sort();
// 查找事件的一些修正:绑定,代理,触发,移除
/*
特别声明:
后续的代码中,将会频繁的看到 special 关键词
这是一个修正对象,就像以前在设置属性的时候,部分属性需要进行特殊的处理,
进而映射成 hooks 对象,在本系列的最后小编将会整理好有特殊处理的各个事件
暂且,如果有对special对象的操作不理解的,可以先跳过,
就假定全部相关的if判断全部通过即可
*/
special = jQuery.event.special[ type ] || {};
// 如果 selector 存在,则启用事件代理,需要把不冒泡的事件, 修正为可以冒泡的
// 不传入 selector , 则将 不支持或者支持的不完整的修正为之支持度更好的事件
type = ( selector ? special.delegateType : special.bindType ) || type;
// 到这里时也许type是被修正过的了,所以这里要再次取一下修正的对象
special = jQuery.event.special[ type ] || {};
// 这里是对以上的元素综合整理后的 处理对象
handleObj = jQuery.extend({
//修正完的eventType
type: type,
//初始化时传入的eventType
origType: tns[1],
//函数触发时可以传入的自定义参数
data: data,
//监听处理函数
handler: handler,
//处理函数的标示
guid: handler.guid,
//事件代理时指定的子对象
selector: selector,
//这里是对selector字符串的进一步解析 以便后更快捷的使用
//"div#con"=> [ "div#con", "div", "con", undefined" ]
//"div.cla"=> [ "div.cla", "div", undefined, /(?:^|\s)cla(?:\s|$)/ ]
// 1=>elem, 2=>id, 3=>classReg
quick: selector && quickParse( selector ),
//命名空间,此时是排序后的
namespace: namespaces.join(".")
}, handleObjIn );
// 去事件存储对象中取出事件队列
handlers = events[ type ];
if ( !handlers ) {
//这里对type类型的队列进行初始化
handlers = events[ type ] = [];
//初始化计数器
//用于指向哪个函数是委托的
handlers.delegateCount = 0;
// 修正对象里要是存在setup则先调用setup,返回false则继续执行绑定
if( !special.setup ||
special.setup.call( elem, data, namespaces, eventHandle ) === false ){
// 绑定事件
if( elem.addEventListener ){
elem.addEventListener( type, eventHandle, false );
}else if( elem.attachEvent ){
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ){
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
/*
这里有点意思,如果本次绑定的是 委托,
则将他插入到队列的头部,使用一个计数器来进行和普通函数进行区别
0 - delegateCount => 委托事件
delegateCount - end => 普通事件
*/
// 事件委托
if ( selector ) {
//将委托函数放插入到 函数队列中,handlers.delegateCount指向的位置
handlers.splice( handlers.delegateCount++, 0, handleObj );
//普通处理函数,直接插入队列
} else {
handlers.push( handleObj );
}
// 做个标记,表示该类型的事件被绑定过
jQuery.event.global[ type ] = true;
}
// 释放内存
elem = null;
},
//...
};
本篇就介绍到这里,总结下:
通过很多形式开放的api方便用户的各种绑定操作 ↓
处理各种情况的参数,整理完毕后调用统一的内部处理函数 ↓
将数据进行缓存,同时绑定到真实DOM元素上 ↓
返回this(jQ对象)
感谢您的阅读,欢迎留言:斧正,交流,提问;