上文提到,jquery的事件绑定有bind(),delegate()和one()以及live()方式。我用的jQuery2.1.3版本,live()已经被废弃了。
//7491行
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
//7498行
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
//7474行
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 ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
//4859行
one: function( types, selector, data, fn ) {
return this.on( types, selector, data, fn, 1 );
},
以下这几个函数全都指向了on方法。
//4806行
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type;
// Types can be a map of types/handlers
// types可以是一个由types/handlers组成的map对象
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
// 如果selector不是字符串
// 则将传参由( types-Object, selector, data )变成( types-Object, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
//遍历所有type
for ( type in types ) {
//添加type事件处理函数
this.on( type, selector, data, types[ type ], one );
}
return this;
}
// 如果data为空,且fn为空
if ( data == null && fn == null ) {
// 传参变成( types, fn )
fn = selector;
data = selector = undefined;
// 否则如果只是fn为空
} else if ( fn == null ) {
// 如果selector为字符串
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为false则变成一个return false的函数
fn = returnFalse;
} else if ( !fn ) {
//如果fn现在还不存在,则直接return this
return this;
}
// 如果one为1,one参数为1说明是函数只执行一次即被废除
if ( one === 1 ) {
//把fn赋值给另一个变量保存
origFn = fn;
//重新定义fn
fn = function( event ) {
// Can use an empty set, since event contains the info
// 这个事件只用一次,用完就用off取消掉。
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
// 使用相同的ID,为了未来好删除事件
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
//最后,对于所有情况,调用jQuery.event.add函数继续处理
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
},
可见,jquery.fn.on函数有以下功能
总的来说就是:通过 on 绑定事件,分析传递的数据,加工变成 add 能够识别的数据。
//4083行
add: function( elem, types, handler, data, selector ) {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
// 通过内部缓存获取元素数据
elemData = data_priv.get( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
//不要把事件添加到没有数据或没有文本的节点(但允许普通的节点)
if ( !elemData ) {
return;
}
// Caller can pass in an object of custom data in lieu of the handler
//in lieu of 代替
//定位handler的两个参数handler和selector
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// Make sure that the handler has a unique ID, used to find/remove it later
//如果handler没有id,则给它定义一个id,确保每个handler都有一个id,用于未来查找和删除
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure and main handler, if this is the first
//如果是第一次创建,初始化元素的事件结构和主要handler
// 如果缓存数据中没有events数据
if ( !(events = elemData.events) ) {
// 则初始化events
events = elemData.events = {};
}
// 如果缓存数据中没有handle数据
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
// 丢弃jQuery.event.trigger第二次触发事件以及当一个页面被卸载后调用事件
return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
// Handle multiple events separated by a space
// 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组
types = ( types || "" ).match( rnotwhite ) || [ "" ];
// 事件的长度
t = types.length;
// 遍历所有事件
while ( t-- ) {
//尝试取出事件的命名空间,就如上文提到的click.plugin,plugin是命名空间
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;
}
// 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;如果没有selector,就使用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 = 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
//如果特殊事件returnfalse,获取失败,就用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;
}
}
//将事件添加到事件列表handlers,delegate事件(委托事件)放到列表头部,其他事件放到尾部
// 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;
}
},
jQuery.event.add函数以下功能:
// 通过内部缓存获取元素数据
elemData = data_priv.get( elem );
// Make sure that the handler has a unique ID, used to find/remove it later
//如果handler没有id,则给它定义一个id,确保每个handler都有一个id,用于未来查找和删除
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure and main handler, if this is the first
//如果是第一次创建,初始化元素的事件结构和主要handler
// 如果缓存数据中没有events数据
if ( !(events = elemData.events) ) {
// 则初始化events
events = elemData.events = {};
}
// 如果缓存数据中没有handle数据
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
// 丢弃jQuery.event.trigger第二次触发事件以及当一个页面被卸载后调用事件
return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
//eleData结构
elemData = {
events:{}
eventHandle:function(){}
}
总的来说就是:
1. 通过 add 把数据整理放到数据缓存中保存,通过 addEventListener 绑定事件
2. 触发事件执行 addEventListener 回调 dispatch 方法
jquery2.1.3没有做IE8以前的事件兼容,默认放弃了IE8以前的版本,没有做attachEvent事件的兼容处理。
// 4391行
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
// 重写原生事件对象,变成一个可读写的对象,方便未来修改、扩展
event = jQuery.event.fix( event );
var i, j, ret, matched, handleObj,
handlerQueue = [],
//参数转数组
args = slice.call( arguments ),
// 从内部数据中查找该元素的对应事件处理器列表中的对应处理器,否则为空数组
handlers = ( data_priv.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;
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
// 尝试使用特殊事件的preDispatch钩子来绑定事件,并在必要时退出
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
// Determine handlers
// 组装事件处理包{elem, handlerObjs}(这里是各种不同元素)的队列。
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
// Run delegates first; they may want to stop propagation beneath us
i = 0;
// 遍历事件处理包{elem, handlerObjs}(取出来则对应一个包了),且事件不需要阻止冒泡
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
// 定义当前Target为事件处理对象对应的元素
event.currentTarget = matched.elem;
j = 0;
// 如果事件处理对象{handleObjs}存在(一个元素可能有很多handleObjs),且事件不需要立刻阻止冒泡
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).
// 触发的事件必须满足其一:
// 1) 没有命名空间
// 2) 有命名空间,且被绑定的事件是命名空间的一个子集
if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
event.handleObj = handleObj;
event.data = handleObj.data;
// 尝试通过特殊事件获取处理函数,否则使用handleObj中保存的handler(所以handleObj中还保存有handler)
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
// 如果处理函数存在
if ( ret !== undefined ) {
// 如果处理函数返回值是false,则阻止冒泡,阻止默认动作
if ( (event.result = ret) === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// Call the postDispatch hook for the mapped type
// 尝试通过special.postDispatch勾住这个映射关系,未来可以优化
if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}
// 返回事件函数
return event.result;
},
这里首先就调用了 jQuery.event.fix( event )函数对事件做了兼容处理。
//4496行 fix函数以及fix函数用到的函数们
// Includes some event props shared by KeyEvent and MouseEvent
//props 存储了原生事件对象 event 的通用属性
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
fixHooks: {},
//keyHook.props 存储键盘事件的特有属性
keyHooks: {
props: "char charCode key keyCode".split(" "),
filter: function( event, original ) {
// Add which for key events
if ( event.which == null ) {
event.which = original.charCode != null ? original.charCode : original.keyCode;
}
return event;
}
},
//mouseHooks.props 存储鼠标事件的特有属性。
mouseHooks: {
props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
//keyHooks.filter 和 mouseHooks.filter 两个方法分别用于修改键盘和鼠标事件的属性兼容性问题,用于统一接口。
filter: function( event, original ) {
var eventDoc, doc, body,
button = original.button;
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && original.clientX != null ) {
eventDoc = event.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
}
// Add which for click: 1 === left; 2 === middle; 3 === right
// Note: button is not normalized, so don't use it
if ( !event.which && button !== undefined ) {
event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
}
return event;
}
},
//大家看过来啦,,fix函数开始了
fix: function( event ) {
if ( event[ jQuery.expando ] ) {
return event;
}
// Create a writable copy of the event object and normalize some properties
var i, prop, copy,
type = event.type,
originalEvent = event,
fixHook = this.fixHooks[ type ];
//判断事件的类型,是mouse事件还是key事件
if ( !fixHook ) {
this.fixHooks[ type ] = fixHook =
rmouseEvent.test( type ) ? this.mouseHooks :
rkeyEvent.test( type ) ? this.keyHooks :
{};
}
//实际上就是判断事件是mouse事件还是key事件,如果是mouse事件把props和mouse事件的props都加到事件的属性里
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
event = new jQuery.Event( originalEvent );
//jQuery 自己写了一个基于 native event 的 Event 对象,并且把 copy 数组中对应的属性从 native event 中复制到自己的 Event 对象中。
i = copy.length;
while ( i-- ) {
prop = copy[ i ];
event[ prop ] = originalEvent[ prop ];
}
// Support: Cordova 2.5 (WebKit) (#13255)
// All events should have a target; Cordova deviceready doesn't
if ( !event.target ) {
event.target = document;
}
// Support: Safari 6.0+, Chrome<28
// Target should not be a text node (#504, #13143)
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
//在最后 jQuery 还不忘放一个钩子,调用 fixHook.fitler 方法用以纠正一些特定的 event 属性。例如 mouse event 中的 pageX,pageY,keyboard event中的 which,进一步修正事件对象属性的兼容问题。
return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
},
fix函数实际上就是将js原生事件的通用属性重写了一遍,附加到jquery自己创建的事件对象上面了。
为什么将浏览器原生 Event 的属性赋值到新创建的 jQuery.Event 对象中去哪?jQuery要增加自己的处理机制,这样更灵活,而且还可以传递 data 数据,也就是用户自定义的数据。
fix函数总结:
1.将原生的事件对象 event 修正为一个新的可写 event 对象,并对该 event 的属性以及方法统一接口
2.该方法在内部调用了 jQuery.Event(event) 构造函数
回到depatch函数,调用fix函数之后
//4450行
handlers: function( event, handlers ) {
var i, matches, sel, handleObj,
handlerQueue = [],
delegateCount = handlers.delegateCount,
//获取事件的触发元素
cur = event.target;
// Find delegate handlers
// Black-hole SVG
// Avoid non-left-click bubbling in Firefox (#3861)
//如果有delegateCount属性,代表事件时delegate类型事件(即事件委托)
// 找出所有delegate的处理函数列队
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
// 遍历元素及元素父级节点
for ( ; cur !== this; cur = cur.parentNode || this ) {
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
// 防止单机被禁用的元素时触发事件
if ( cur.disabled !== true || event.type !== "click" ) {
// 开始组装符合要求的事件处理对象
matches = [];
// 遍历所有事件处理对象,还记得吗,add方法里面,delegate类型的事件都放在handlers队列的最前面了
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203) // 选择器,用于过滤
sel = handleObj.selector + " ";
// 如果matches上没有绑定该选择器数量
if ( matches[ sel ] === undefined ) {
// 在matches上绑定该选择器数量
matches[ sel ] = handleObj.needsContext ?
// 得出选择器数量,并赋值
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
// 再次确定是否绑定选择器数量
if ( matches[ sel ] ) {
// 是则将事件处理对象推入
matches.push( handleObj );
}
}
// 如果得到的matches里有事件处理对象
if ( matches.length ) {
// 组装成事件处理包(暂时这么叫吧),推入事件处理包队
handlerQueue.push({ elem: cur, handlers: matches });
}
}
}
}
// Add the remaining (directly-bound) handlers
// 如果还有事件剩余,则将剩余的装包,推入列队
if ( delegateCount < handlers.length ) {
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
}
return handlerQueue;
},
handlers函数有什么作用呢?
现在有一个问题。假设一段jquery代码是这样的。
<div id="aaron">
<div id='test'>
<ul>
<p>点击测试委托顺序p>
ul>
div>
div>
var ul = $('ul')
function show(data){
ul.append('' + data +'')
}
var aaron = $("#aaron")
//同一个元素上绑定不同的事件委托
aaron.on('mousedown','p',function(e){
show('p')
e.stopPropagation()
})
aaron.on('mousedown','ul',function(e){
show('被阻止了')
})
$("#test").on('mousedown',function(){
show('test')
})
运行结果:
text p
发现“被阻止了”没有打印出来。
arron有两个事件委托。一个是子孙元素p元素委托arron,另一个是ul元素委托。当p元素委托之后,callback函数里明确规定stopPropagation,这时ul元素绑定的事件不能触发。
handlers函数就是为了实现这个目标哒。
handlers有如下需求
- 根据冒泡的原理,不管事件添加的顺序如何,应该为elem上的所有事件依照触发顺序排出一个层次来,最里的委托优先级最高,最外的委托优先级最低,这样停止冒泡的需求得以实现。
handlers实现原理
还有一个special事件的一堆函数,下次再写喽。