迎接来我的专栏检察系列文章。
经由过程前面一章关于 addEvent 库的引见,它的兼容性超等棒,听说关于 IE4、5 都有很好的兼容性,这和 jQuery 的道理是一致的,而在 jQuery 中,有一个对象与其相对应,那就是 event。
上上章就已说过了,这个 jQuery.fn.on
这个函数终究是经由过程 jQuery.event
对象的 add 要领来完成功用的,固然,要领不局限于 add,下面就要对这些要领举行细致的引见。
关于 jQuery.event
这是一个在 jQuery 对象上的要领,它做了许多的优化事宜,比方兼容性题目,存储优化题目。
jQuery.event = {
global = {},
add: function(){...},
remove: function(){...},
dispatch: function(){...},
handlers: function(){...},
addProp: function(){...},
fix: function(){...},
special: function(){...}
}
上面是 event 上的一些函数,个中:
add()
是增加事宜函数,在当前 dom 上监听事宜,天生处置惩罚函数;remove()
移除事宜;dispatch()
是现实的事宜实行者;handlers()
在 dispatch 实行的时刻,对事宜举行校订,辨别原生与托付事宜;addProp()
是绑定参数到对象上;fix()
将原生的 event 事宜修复成一个可读可写且有一致接口的对象;special()
是一个特别事宜表。
说 add 函数之前,照样不由得要把 Dean Edwards 大神的 addEvent 库来品尝一下,只管之前已谈过了:
function addEvent(element, type, handler) {
// 给每一个要绑定的函数增加一个标识 guid
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
// 在绑定的对象事宜上竖立一个事宜对象
if (!element.events) element.events = {};
// 一个 type 对应一个 handlers 对象,比方 click 可同时处置惩罚多个函数
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// 假如 onclick 已存在一个函数,拿过来
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// 防备反复绑定,每一个对应一个 guid
handlers[handler.$$guid] = handler;
// 把 onclick 函数替换成 handleEvent
element["on" + type] = handleEvent;
};
// 初始 guid
addEvent.guid = 1;
function removeEvent(element, type, handler) {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
// 觉得背面是否是要加个推断,当 element.events[type] 为空时,一同删了
};
function handleEvent(event) {
// grab the event object (IE uses a global event object)
event = event || window.event;
// 这里的 this 指向 element
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
// 这里有个小技能,为何不直接实行,而是先绑定到 this 后实行
// 是为了让函数实行的时刻,内部 this 指向 element
this.$$handleEvent = handlers[i];
this.$$handleEvent(event);
}
};
假如我们对这个浏览器支撑 addEventListener
,我们便可以对 addEvent 函数就行轻微对小修正(暂不斟酌 attachEvent 兼容),在 addEvent 函数的末了,假如代码修正成以下:
//element["on" + type] = handleEvent;
if(!element.hasAddListener){
element.addEventListener(type,handleEvent,false);
// 监听事宜只需增加一次就好了
element['hasAddListener'] = true;
}
虽然以上的做法有点反复,须要对基础逻辑举行推断,但这已异常靠近 jQuery 中的事宜 add 要领。换句话说,之前的逻辑是把一切的监听要领都经由过程 addEventListener
来绑定,绑定一个,绑定两个,如今的思绪变了:假如我们写一个处置惩罚函数(handleEvent),这个处置惩罚函数用来处置惩罚绑定到 DOM 上的事宜,并经由过程 addEventListener 增加(只需增加一次),这就是 jQuery 中事宜处置惩罚的基础逻辑(我所明白的,迎接斧正)。
懂了上面,还须要清晰托付事宜的实质:在父 DOM 上监听事宜,事宜处置惩罚函数找到对应的子 DOM 来处置惩罚。
jQuery.event.add 函数剖析
好了,来直接看源码,我已不止一次的提到,进修源码最好的体式格局是调试,最有用的调试是覆蓋率 100% 的测试用例。
jQuery.event = {
global: {},
add: function( elem, types, handler, data, selector ) {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
// jQuery 特地为事宜竖立一个 data cache:dataPriv
elemData = dataPriv.get( elem );
// elem 有题目,直接退出
if ( !elemData ) {
return;
}
// 假如 handler 是一个事宜处置惩罚对象,且有 handler 属性
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// Ensure that invalid selectors throw exceptions at attach time
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
if ( selector ) {
jQuery.find.matchesSelector( documentElement, selector );
}
// guid。
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// 初始化 data.elem 的 events 和 handle
if ( !( events = elemData.events ) ) {
events = elemData.events = {};
}
if ( !( eventHandle = elemData.handle ) ) {
eventHandle = elemData.handle = function( e ) {
// 终究的实行在这里,点击 click 后,会实行这个函数
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
// 处置惩罚多个事宜比方 ("click mouseout"),空格离隔
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
t = types.length;
// 每一个事宜都处置惩罚
while ( t-- ) {
tmp = rtypenamespace.exec( types[ t ] ) || [];
type = origType = tmp[ 1 ];
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
// 特别处置惩罚
special = jQuery.event.special[ type ] || {};
// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;
// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};
// handleObj is passed to all event handlers
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 );
// 假如 click 事宜之前没有增加过,
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 ) {
// eventHandle 才是事宜处置惩罚函数
elem.addEventListener( type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// 增加到事宜列表, delegates 要在前面
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
// 外表已增加过了
jQuery.event.global[ type ] = true;
}
}
}
来重新理一下代码,之前就已说过 jQuery 中 Data 的题目,这里有两个:
// 用于 DOM 事宜
var dataPriv = new Data();
// jQuery 中通用
var dataUser = new Data();
dataPriv 会依据当前的 elem 缓存两个对象,分别是 events 和 handle,这个 handle 就是经由过程 addEventListener 增加的谁人回掉函数,而 events 存储的东西较多,比方我绑定了一个 click 事宜,则 events['click'] = []
,它是一个数组,这个时刻不管我绑定多少个点击事宜,只须要在这个数组内里增加内容即可,增加的时刻要斟酌肯定的递次。那末,数组的每一个子元素长什么样:
handleObj = {
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join( "." )
}
兼容性,兼容性,兼容性,实在 add 事宜重要照样斟酌到许多细节的内容,假如把这些都抛开不开,我们来比较一下 event.add
函数和 addEvent
函数相同点:
// 左侧是 addEvent 函数中内容
addEvent == jQuery.event.add;
handleEvent == eventHandle;
handler.$$guid == handler.guid;
element.events == dataPriv[elem].events;
异常像!
jQuery.event.dispatch 函数剖析
当有 selector 的时刻,add 函数处置惩罚增加事宜,而事宜的实行,要靠 dispatch。举个例子,在 $('body').on('click','#test',fn)
,我们点击 body,会被监听,dispatch 函数是会实行的,然则 fn 不实行,除非我们点击 #test
。也许 dispath 就是用来推断 event.target
是否是须要的谁人。
jQuery.event.extend( {
dispatch: function( nativeEvent ) {
// 把 nativeEvent 变成可读写,jQuery 承认的 event
var event = jQuery.event.fix( nativeEvent );
var i, j, ret, matched, handleObj, handlerQueue,
args = new Array( arguments.length ),
// 从 data cache 中搜刮处置惩罚事宜
handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[ 0 ] = event;
for ( i = 1; i < arguments.length; i++ ) {
args[ i ] = arguments[ i ];
}
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
// 对 handlers 处置惩罚,辨别事宜范例,并根据递次排好
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
// Run delegates first; they may want to stop propagation beneath us
i = 0;
while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
event.currentTarget = matched.elem;
j = 0;
// 根据 handlers 排好的递次,一次实行
while ( ( handleObj = matched.handlers[ j++ ] ) &&
!event.isImmediatePropagationStopped() ) {
// Triggered event must either 1) have no namespace, or 2) have namespace(s)
// a subset or equal to those in the bound event (both can have no namespace).
if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
event.handleObj = handleObj;
event.data = handleObj.data;
// 终究的实行在这里
ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
handleObj.handler ).apply( matched.elem, args );
if ( ret !== undefined ) {
if ( ( event.result = ret ) === false ) {
// 下面两个函数是经由 event 革新后的事宜
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// Call the postDispatch hook for the mapped type
if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}
return event.result;
}
} );
event.handlers
的逻辑也是十分庞杂的,而且我看了,也没看懂,大抵就是将一切绑定到 elem 上的事宜,根据肯定的递次来辨别他们的实行递次。
总结
花了好几天,也只是看了一个外相罢了,尤其是对个中的事宜逻辑,觉得好庞杂。也只能如许了。
参考
jQuery 2.0.3 源码剖析 事宜体系结构
解密jQuery事宜中心 – 绑定设想(一)
解密jQuery事宜中心 – 托付设想(二)
本文在 github 上的源码地点,迎接来 star。
迎接来我的博客交换。