jQuery源码解析之on事件绑定
本⽂采⽤的jQuery源码为
jquery的on⽅法⽤来在选定的元素上绑定⼀个或多个事件处理函数。
当参数selector存在时,通常会⽤来对已经存在的元素或将来即将添加到⽂档中的元素做事件委托,表⽰当点击document中的selector元素时,将触发function回调函数。
1 <div id="div" >
2 <p id="paragraph">test events </p>
3 </div>
4
5 <script src="../jquery-3.2.1.js"></script>
6 <script>
7 $(function(){
8 $(document).on("click","#paragraph",function(){
9 console.log('this is paragraph');
10 });
11 $(document).on("click","#div",function(){
12 console.log('this is div');
13 });
14 $(document).on("click","#addDiv",function(){
15 console.log('this is add div');
16 });
17
18 setTimeout(function(){
19 createDiv();
20 },2000);
21 });
22
23function createDiv(){
24 $("<div>").attr("id","addDiv").html("this is a div add after event binding").appendTo($("body"));
25 }
26 </script>
上⾯例⼦中三个事件都绑定在document对象上,当⿏标点击最内层的#paragraph时,会看到浏览器打出的结果是
可以得知,绑定的事件是在冒泡阶段触发的。
查看源码
1 d( {
2
3 on: function( types, selector, data, fn ) {
4return on( this, types, selector, data, fn );
5 },
6//.....
7
8//on⽅法对传⼊的参数做了⼀些转换
9function on( elem, types, selector, data, fn, one ) {
10var origFn, type;
11
12// Types can be a map of types/handlers
13if ( typeof types === "object" ) {
14
15// ( types-Object, selector, data )
16if ( typeof selector !== "string" ) {
17
18// ( types-Object, data )
19 data = data || selector;
20 selector = undefined;
21 }
22for ( type in types ) {
23 on( elem, type, selector, data, types[ type ], one );
24 }
25return elem;
26 }
27
jquery源码在线28if ( data == null && fn == null ) {
29
30// ( types, fn )
31 fn = selector;
32 data = selector = undefined;
33 } else if ( fn == null ) {
34if ( typeof selector === "string" ) {
35
36// ( types, selector, fn )
37 fn = data;
38 data = undefined;
39 } else {
40
41// ( types, data, fn )
42 fn = data;
43 data = selector;
44 selector = undefined;
45 }
46 }
47if ( fn === false ) {
48 fn = returnFalse;
49 } else if ( !fn ) {
50return elem;
51 }
52
53if ( one === 1 ) {
54 origFn = fn;
55 fn = function( event ) {
56
57// Can use an empty set, since event contains the info
58 jQuery().off( event );
59return origFn.apply( this, arguments );
60 };
61
62// Use same guid so caller can remove using origFn
63 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
64 }
65//最终的结果由jQuery.event.add⽅法实现
66return elem.each( function() {
67 jQuery.event.add( this, types, fn, data, selector );
68 } );
69 }
1 add: function( elem, types, handler, data, selector ) {
2
3var handleObjIn, eventHandle, tmp,
4 events, t, handleObj,
5 special, handlers, type, namespaces, origType,
6 elemData = ( elem );//此处从内存中获取document对象的数据,
//第⼀次绑定时是没有数据的,程序将执⾏cache⽅法,创建⼀个{}值作为document的值,并返回该值的引⽤。
//若内存中已存在document的数据,则直接返回。
//此时elemData为document对象在内存中的数据的引⽤,下⾯将为它赋值
cache: function( owner ) {
// Check if the owner object already has a cache
var value = owner[ pando ];
// If not, create one
if ( !value ) {
value = {};
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return an empty object.
if ( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if ( deType ) {
owner[ pando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
} else {
Object.defineProperty( owner, pando, {
value: value,
configurable: true
} );
}
}
}
return value;
},
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ pando ] && owner[ pando ][ jQuery.camelCase( key ) ];
},
7
8// Don't attach events to noData or text/comment nodes (but allow plain objects)
9if ( !elemData ) {
10return;
11 }
12
13// Caller can pass in an object of custom data in lieu of the handler
14if ( handler.handler ) {
15 handleObjIn = handler;
16 handler = handleObjIn.handler;
17 selector = handleObjIn.selector;
18 }
19
20// Ensure that invalid selectors throw exceptions at attach time
21// Evaluate against documentElement in case elem is a non-element node (e.g., document)
22if ( selector ) {
23 jQuery.find.matchesSelector( documentElement, selector );
24 }
25
26// Make sure that the handler has a unique ID, used to find/remove it later
27if ( !handler.guid ) {
28 handler.guid = jQuery.guid++;
29 }
30
31// Init the element's event structure and main handler, if this is the first
32if ( !( events = elemData.events ) ) {
33 events = elemData.events = {};//为elemData添加events对象属性,
34 }
35if ( !( eventHandle = elemData.handle ) ) {
36 eventHandle = elemData.handle = function( e ) {//事件触发时,调⽤该函数;为elemData添加handle⽅法
37
38// Discard the second event of a igger() and
39// when an event is called after a page has unloaded
40return typeof jQuery !== "undefined" && iggered !== e.type ?
41 jQuery.event.dispatch.apply( elem, arguments ) : undefined;
42 };
43 }
44
45// Handle multiple events separated by a space
46 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
47 t = types.length;
48while ( t-- ) {
49 tmp = ( types[ t ] ) || [];
50 type = origType = tmp[ 1 ];
51 namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
52
53// There *must* be a type, no attaching namespace-only handlers
54if ( !type ) {
55continue;
56 }
57
58// If event changes its type, use the special event handlers for the changed type
59 special = jQuery.event.special[ type ] || {}; 60
61// If selector defined, determine special event api type, otherwise given type
62 type = ( selector ? special.delegateType : special.bindType ) || type;
63
64// Update special based on newly reset type
65 special = jQuery.event.special[ type ] || {};
66
67// handleObj is passed to all event handlers
68 handleObj = d( {//将事件绑定时传⼊的参数:事件类型、选择器、回调函数等封装⼊handleObj对象中
69 type: type,
70 origType: origType,
71 data: data,
72 handler: handler,
73 guid: handler.guid,
74 selector: selector,
75 needsContext: selector && st( selector ),
76 namespace: namespaces.join( "." )
77 }, handleObjIn );
78
79// Init the event handler queue if we're the first
80if ( !( handlers = events[ type ] ) ) {
81 handlers = events[ type ] = [];//为elemData.events.click赋值为[],同时handlers指向该数组
82 handlers.delegateCount = 0;
83
84// Only use addEventListener if the special events handler returns false
85if ( !special.setup ||
86 special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
87
88if ( elem.addEventListener ) {//绑定事件,注意事件是绑定到elem上的,即document对象上
89 elem.addEventListener( type, eventHandle );
90 }
91 }
92 }
93
94if ( special.add ) {
95 special.add.call( elem, handleObj );
96
97if ( !handleObj.handler.guid ) {
98 handleObj.handler.guid = handler.guid;
99 }
100 }
101
102// Add to the element's handler list, delegates in front
103if ( selector ) {
104 handlers.splice( handlers.delegateCount++, 0, handleObj );
/**将handleObj中的属性插⼊到handlers中,即document在内存中的数据={handle:f(),
events:{click:[0:{type: "click", origType: "click", data: undefined, guid: 1, handler: ƒ(),
selector:"#div",namespace:"",needContext:false},
delegateCount:1]}}**/
105 } else {
106 handlers.push( handleObj );
107 }
108
109// Keep track of which events have ever been used, for event optimization
110 jQuery.event.global[ type ] = true;
111 }
112
113 },//事件绑定完成
由事件绑定过程可以看出,事件触发时执⾏eventHandle函数,⽽eventHanle最终执⾏事件派发:jQuery.event.dispatch.apply( elem, arguments )
1 dispatch: function( nativeEvent ) {
2
3// Make a writable jQuery.Event from the native event object
4var event = jQuery.event.fix( nativeEvent );//将原⽣事件对象转化为jquery的event
5
6var i, j, ret, matched, handleObj, handlerQueue,
7 args = new Array( arguments.length ),
8 handlers = ( ( this, "events" ) || {} )[ pe ] || [],//获取事件绑定时document存储的参数值
9 special = jQuery.event.special[ pe ] || {};
10
11// Use the fix-ed jQuery.Event rather than the (read-only) native event
12 args[ 0 ] = event;
13
14for ( i = 1; i < arguments.length; i++ ) {
15 args[ i ] = arguments[ i ];
16 }
17
18 event.delegateTarget = this;
19
20// Call the preDispatch hook for the mapped type, and let it bail if desired
21if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
22return;
23 }
24
25// Determine handlers
26 handlerQueue = jQuery.event.handlers.call( this, event, handlers );//从document的所有事件参数中,筛选出当前点击⽬标的参数对象
handlers: function( event, handlers ) {
var i, handleObj, sel, matchedHandlers, matchedSelectors,
handlerQueue = [],
delegateCount = handlers.delegateCount,//委派次数
cur = event.target;//委派的⽬标,如#addDiv
// Find delegate handlers
if ( delegateCount &&
// Support: IE <=9
// Black-hole SVG <use> instance trees (trac-13180)
// Support: Firefox <=42
// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
// /TR/DOM-Level-3-Events/#event-type-click
// Support: IE 11 only
/
/ ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
!( pe === "click" && event.button >= 1 ) ) {
for ( ; cur !== this; cur = cur.parentNode || this ) {
// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if ( deType === 1 && !( pe === "click" && cur.disabled === true ) ) {
matchedHandlers = [];
matchedSelectors = {};
for ( i = 0; i < delegateCount; i++ ) {//遍历委派的事件参数数组,当selector=当前点击对象cur时,将对应的参数对象放⼊handlerQueue中
//注意:遍历时对于selector指向的页⾯元素,⽆论它是页⾯加载时已经存在的元素,还是页⾯加载完成后通过js后来⽣成的元素,当点击该元素时(此时该元素已经存在了), //程序便能实现对其回
调函数的调⽤。这也是为什么on可以对未来出现的元素进⾏事件绑定了。
//当点击的是⼦级对象时,若它的⽗级也绑定了相同的事件,则⽗级相关数据也⼀并返回
handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " ";
if ( matchedSelectors[ sel ] === undefined ) {
matchedSelectors[ sel ] = dsContext ?
jQuery( sel, this ).index( cur ) > -1 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( matchedSelectors[ sel ] ) {
matchedHandlers.push( handleObj );
}
}
if ( matchedHandlers.length ) {
handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
}
}
}
}
// Add the remaining (directly-bound) handlers
cur = this;
if ( delegateCount < handlers.length ) {
handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
}
return handlerQueue;
},
28// Run delegates first; they may want to stop propagation beneath us
29 i = 0;//最终执⾏时,⼦级和⽗级在同⼀次事件触发时同时执⾏(因为点击的是⼦级)
30while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
31 event.currentTarget = matched.elem;
32
33 j = 0;
34while ( ( handleObj = matched.handlers[ j++ ] ) &&
35 !event.isImmediatePropagationStopped() ) {
//没有调⽤event.stopImmediatePropagation() ⽅法,即没有阻⽌事件传播
36
37// Triggered event must either 1) have no namespace, or 2) have namespace(s)
38// a subset or equal to those in the bound event (both can have no namespace).
39if ( !amespace || st( handleObj.namespace ) ) {
40
41 event.handleObj = handleObj;
42 event.data = handleObj.data;
43
44 ret = ( ( jQuery.event.special[ igType ] || {} ).handle ||
45 handleObj.handler ).apply( matched.elem, args );
//执⾏回调函数matched.elem.func(args);
//突然发现原来是⽬标elem执⾏的func回调函数,这也是为什么回调函数中的$(this)会指向当前绑定的jquery对象了。
46
47if ( ret !== undefined ) {
//如果回调函数返回值为false时,则阻⽌事件的默认操作和后续的冒泡传播
48if ( ( sult = ret ) === false ) {
49 event.preventDefault();
50 event.stopPropagation();
51 }
52 }
53 }
54 }
55 }
56
57// Call the postDispatch hook for the mapped type
58if ( special.postDispatch ) {
59 special.postDispatch.call( this, event );
60 }
61
sult;
63 },
但程序是如何取到document在绑定事件时存储在内存中的数据的呢?
可以看到,我们获取内存中的数据时是通过dataPriv对象来获取的,页⾯加载时会⾃动创建dataPriv对象,⾥⾯包含当前⽂档中唯⼀的expando 扩展属性。
1var dataPriv = new Data();
2//......
3
4function Data() {
6 }
7 Data.uid = 1;
8//......
9
d( {
11
12// Unique for each copy of jQuery on the page
13 expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
14//......
当第⼀次为document对象创建内存对象时,内存对象关联到expando属性;当事件触发时,通过()⽅法获取到document对应的expando的值对象。这样就保证了事件前后的数据统⼀。
cache: function( owner ) {
// Check if the owner object already has a cache
var value = owner[ pando ];
// If not, create one
if ( !value ) {
value = {};
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return an empty object.
if ( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if ( deType ) {
owner[ pando ] = value;//即document[jQuery3210080552146542722581]={}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论