前言:
这篇依旧长,请耐心看下去。
一、事件委托
DOM有个事件流特性,所以触发DOM节点的时候,会经历3个阶段:
(1)阶段一:Capturing 事件捕获(从祖到目标)
在事件
自上(document->html->body->xxx)而下到达目标节点的过程中,浏览器会检测 针对该事件的 监听器(用来捕获事件),并运行捕获事件的监听器。
(2)阶段二:Target 目标
浏览器找到监听器后,就运行该监听器
(3)阶段三:Bubbling 冒泡(目标到祖)
在事件
自下而上(document->html->body->xxx)到达目标节点的过程中,浏览器会检测不是 针对该事件的 监听器(用来捕获事件),并运行非捕获事件的监听器。
二、$()
.click()
作用:
为目标元素绑定点击事件
源码:
//这种写法还第一次见,将所有鼠标事件写成字符串再换成数组
//再一一绑定到DOM节点上去
//源码10969行
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
function( i, name ) {
//事件绑定
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
//如果有参数的话,就用jQuery的on绑定
this.on( name, null, data, fn ) :
//否则使用trigger
this.trigger( name );
};
} );
解析:
可以看到,jQuery 将所有的鼠标事件都一一列举了出来,并通过jQuery.fn[ name ] = function( data, fn ) { xxx }
如果有参数,则是绑定事件,调用 on() 方法;
没有参数,则是调用事件,调用 trigger() 方法( trigger() 放到下篇讲 )
三、$()
.on()
作用:
在被选元素及子元素上添加一个或多个事件处理程序
源码:
//绑定事件的方法
//源码5812行
jQuery.fn.extend( {
//在被选元素及子元素上添加一个或多个事件处理程序
//$().on('click',function()=<{})
//源码5817行
on: function( types, selector, data, fn ) {
return on( this, types, selector, data, fn );
},
//xxx
//xxx
})
最终调用的是 jQuery.on() 方法:
//绑定事件的on方法
//源码5143行
//目标元素,类型(click,mouseenter,focusin,xxx),回调函数function(){xxx}
function on( elem, types, selector, data, fn, 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 ) {
on( elem, type, selector, data, types[ type ], one );
}
return elem;
}
//直接调用$().on()的话会走这边
if ( data == null && fn == null ) {
// ( types, fn )
//fn赋值为selector,即function(){}
fn = selector;
//再将selector置为undefined
//注意这个写法,连等赋值
data = selector = undefined;
}
//调用像$().click()的话会走这边
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 elem;
}
//one()走这里
if ( one === 1 ) {
//将fn赋给origFn后,再定义fn
origFn = fn;
fn = function( event ) {
//将绑定给目标元素的事件传给fn,
//并通过$().off()卸载掉
// Can use an empty set, since event contains the info
jQuery().off( event );
//在origFn运行一次的基础上,让origFn调用fn方法,arguments即event
return origFn.apply( this, arguments );
};
//让fn和origFn使用相同的guid,这样就能移除origFn方法
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return elem.each( function() {
//最终调动$.event.add方法
jQuery.event.add( this, types, fn, data, selector );
} );
}
解析:
可以看到,由于将 bind()、live() 和 delegate() 都合并进 on() 后,on() 里面的情况挺复杂的, data、selector、fn 相互赋值。
注意下 if ( one === 1 )
这种情况,是 $().one()
在on()
里的具体实现,即调用一次on()
后,就执行jQuery().off( event )
,卸载事件。
该方法最终调用 jQuery.event.add( ) 方法
四、jQuery.event.add( )
作用:
为目标元素添加事件
源码:
//源码5235行
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
jQuery.event = {
global: {},
//源码5241行
//this, types, fn, data, selector
add: function( elem, types, handler, data, selector ) {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
//elemData正是目标元素jQuery中的id属性
//初始值是{}
elemData = dataPriv.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
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 );
}
//确保handler有唯一的id
// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
//如果事件处理没有,则置为空对象
// Init the element's event structure and main handler, if this is the first
//在这里,就应经给events赋值了,
// 注意这种写法:赋值的同时,判断
if ( !( events = elemData.events ) ) {
events = elemData.events = {};
}
if ( !( eventHandle = elemData.handle ) ) {
eventHandle = elemData.handle = function( e ) {
//当在一个页面卸载后调用事件时,取消jQuery.event.trigger()的第二个事件
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
//jQuery.event.triggered: undefined
//e.type: click/mouseout
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
//让elem调用jQuery.event.dispatch方法,参数是arguments
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};
}
//通过空格将多个events分开,一般为一个,如click
// Handle multiple events separated by a space
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[ t ] ) || [];
//click
type = origType = tmp[ 1 ];
//""
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
//如果event改变了它自己的type,就使用特殊的event handlers
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
//如果选择器已定义,确定一个特殊event api的type
//否则使用默认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会传递给所有的event handlers
// 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
if ( !special.setup ||
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
//目标元素有addEventListener的话,调用绑定click事件
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle );
}
}
}
//special的add/handleObj.handler.guidd的初始化处理
if ( special.add ) {
special.add.call( elem, handleObj );
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;
}
},
...
...
}
解析:
可以看到,很多的 if 判断,都是在初始化对象,最后通过 while 循环,调用目标元素的 addEventListener 事件,也就是说,click()/on() 的本质是 element.addEventListener() 事件,前面一系列的铺垫,都是在为目标 jQuery 对象添加必要的属性。
注意写法 if ( !( events = elemData.events ) )
,在赋值的同时,判断条件
(1)dataPriv
//取唯一id
//源码4361行
var dataPriv = new Data();
在 jQuery 对象中,有唯一id的属性
$("#one")
elemData = dataPriv.get( elem )
① Data()
//目标元素的jQuery id
//源码4209行
function Data() {
this.expando = jQuery.expando + Data.uid++;
}
② jQuery.expando
jQuery.extend( {
//相当于jQuery为每一个元素取唯一的id
///\D/g : 去掉非数字的字符
// Unique for each copy of jQuery on the page
//源码360行
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
...
...
})
③ Math.random()
伪随机,到小数点后16位
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
可以看到 jQuery 的 id 是由 jQuery + 版本号+ Math.random() 生成的
关于 Math.random() 是如何生成伪随机数的请看:https://www.zhihu.com/question/22818104
(2)rtypenamespace
var
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
//事件类型的命名空间
//举例:var arr1 = "click.aaa.bbb".match(rtypenamespace);
//console.log(arr1);//["click.aaa.bbb", "click", "aaa.bbb", index: 0, input: "click.aaa.bbb"]
//源码5131行
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
综上,绑定事件的本质即调用element.addEventListener()
方法,但 jQuery 有太多的情况需要考虑了。
(完)