jQuery源码解析之on事件绑定
本文采用的jQuery源码为jquery-3.2.1.js
jquery的on方法用来在选定的元素上绑定一个或多个事件处理函数。
当参数selector存在时,通常会用来对已经存在的元素或将来即将添加到文档中的元素做事件委托,表示当点击document中的selector元素时,将触发function回调函数。
1 <div id="div" style="font-weight:800;font-size:24px;text-align:center;color:red;"> 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 23 function createDiv(){ 24 $("<div>").attr("id","addDiv").html("this is a div add after event binding").appendTo($("body")); 25 } 26 </script>
上面例子中三个事件都绑定在document对象上,当鼠标点击最内层的#paragraph时,会看到浏览器打出的结果是
可以得知,绑定的事件是在冒泡阶段触发的。
查看源码
1 jQuery.fn.extend( { 2 3 on: function( types, selector, data, fn ) { 4 return on( this, types, selector, data, fn ); 5 }, 6 //..... 7 8 //on方法对传入的参数做了一些转换 9 function on( elem, types, selector, data, fn, one ) { 10 var origFn, type; 11 12 // Types can be a map of types/handlers 13 if ( typeof types === "object" ) { 14 15 // ( types-Object, selector, data ) 16 if ( typeof selector !== "string" ) { 17 18 // ( types-Object, data ) 19 data = data || selector; 20 selector = undefined; 21 } 22 for ( type in types ) { 23 on( elem, type, selector, data, types[ type ], one ); 24 } 25 return elem; 26 } 27 28 if ( data == null && fn == null ) { 29 30 // ( types, fn ) 31 fn = selector; 32 data = selector = undefined; 33 } else if ( fn == null ) { 34 if ( 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 } 47 if ( fn === false ) { 48 fn = returnFalse; 49 } else if ( !fn ) { 50 return elem; 51 } 52 53 if ( 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 ); 59 return 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方法实现 66 return elem.each( function() { 67 jQuery.event.add( this, types, fn, data, selector ); 68 } ); 69 }
1 add: function( elem, types, handler, data, selector ) { 2 3 var handleObjIn, eventHandle, tmp, 4 events, t, handleObj, 5 special, handlers, type, namespaces, origType, 6 elemData = dataPriv.get( elem );//此处从内存中获取document对象的数据, //第一次绑定时是没有数据的,程序将执行cache方法,创建一个{}值作为document的值,并返回该值的引用。 //若内存中已存在document的数据,则直接返回。 //此时elemData为document对象在内存中的数据的引用,下面将为它赋值
cache: function( owner ) {
// Check if the owner object already has a cache
var value = owner[ this.expando ];// 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 ( owner.nodeType ) {
owner[ this.expando ] = 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, this.expando, {
value: value,
configurable: true
} );
}
}
}return value;
},get: function( owner, key ) {
return key === undefined ?this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
},
7 8 // Don't attach events to noData or text/comment nodes (but allow plain objects) 9 if ( !elemData ) { 10 return; 11 } 12 13 // Caller can pass in an object of custom data in lieu of the handler 14 if ( 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) 22 if ( 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 27 if ( !handler.guid ) { 28 handler.guid = jQuery.guid++; 29 } 30 31 // Init the element's event structure and main handler, if this is the first 32 if ( !( events = elemData.events ) ) { 33 events = elemData.events = {};//为elemData添加events对象属性, 34 } 35 if ( !( eventHandle = elemData.handle ) ) { 36 eventHandle = elemData.handle = function( e ) {//事件触发时,调用该函数;为elemData添加handle方法 37 38 // Discard the second event of a jQuery.event.trigger() and 39 // when an event is called after a page has unloaded 40 return typeof jQuery !== "undefined" && jQuery.event.triggered !== 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; 48 while ( t-- ) { 49 tmp = rtypenamespace.exec( 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 54 if ( !type ) { 55 continue; 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 = jQuery.extend( {//将事件绑定时传入的参数:事件类型、选择器、回调函数等封装入handleObj对象中 69 type: type, 70 origType: origType, 71 data: data, 72 handler: handler, 73 guid: handler.guid, 74 selector: selector, 75 needsContext: selector && jQuery.expr.match.needsContext.test( selector ), 76 namespace: namespaces.join( "." ) 77 }, handleObjIn ); 78 79 // Init the event handler queue if we're the first 80 if ( !( 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 85 if ( !special.setup || 86 special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 87 88 if ( elem.addEventListener ) {//绑定事件,注意事件是绑定到elem上的,即document对象上 89 elem.addEventListener( type, eventHandle ); 90 } 91 } 92 } 93 94 if ( special.add ) { 95 special.add.call( elem, handleObj ); 96 97 if ( !handleObj.handler.guid ) { 98 handleObj.handler.guid = handler.guid; 99 } 100 } 101 102 // Add to the element's handler list, delegates in front 103 if ( 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 4 var event = jQuery.event.fix( nativeEvent );//将原生事件对象转化为jquery的event 5 6 var i, j, ret, matched, handleObj, handlerQueue, 7 args = new Array( arguments.length ), 8 handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],//获取事件绑定时document存储的参数值 9 special = jQuery.event.special[ event.type ] || {}; 10 11 // Use the fix-ed jQuery.Event rather than the (read-only) native event 12 args[ 0 ] = event; 13 14 for ( 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 21 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { 22 return; 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 handlersif ( delegateCount &&// Support: IE <=9// Black-hole SVG <use> instance trees (trac-13180)cur.nodeType &&// Support: Firefox <=42// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)// https://www.w3.org/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)!( event.type === "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 ( cur.nodeType === 1 && !( event.type === "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 ] = handleObj.needsContext ?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) handlerscur = 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;//最终执行时,子级和父级在同一次事件触发时同时执行(因为点击的是子级) 30 while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { 31 event.currentTarget = matched.elem; 32 33 j = 0; 34 while ( ( 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). 39 if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { 40 41 event.handleObj = handleObj; 42 event.data = handleObj.data; 43 44 ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || 45 handleObj.handler ).apply( matched.elem, args ); //执行回调函数matched.elem.func(args); //突然发现原来是目标elem执行的func回调函数,这也是为什么回调函数中的$(this)会指向当前绑定的jquery对象了。 46 47 if ( ret !== undefined ) { //如果回调函数返回值为false时,则阻止事件的默认操作和后续的冒泡传播 48 if ( ( event.result = ret ) === false ) { 49 event.preventDefault(); 50 event.stopPropagation(); 51 } 52 } 53 } 54 } 55 } 56 57 // Call the postDispatch hook for the mapped type 58 if ( special.postDispatch ) { 59 special.postDispatch.call( this, event ); 60 } 61 62 return event.result; 63 },
但程序是如何取到document在绑定事件时存储在内存中的数据的呢?
可以看到,我们获取内存中的数据时是通过dataPriv对象来获取的,页面加载时会自动创建dataPriv对象,里面包含当前文档中唯一的expando 扩展属性。
1 var dataPriv = new Data(); 2 //...... 3 4 function Data() { 5 this.expando = jQuery.expando + Data.uid++; 6 } 7 Data.uid = 1; 8 //...... 9 10 jQuery.extend( { 11 12 // Unique for each copy of jQuery on the page 13 expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), 14 //......
当第一次为document对象创建内存对象时,内存对象关联到expando属性;当事件触发时,通过dataPriv.get()方法获取到document对应的expando的值对象。这样就保证了事件前后的数据统一。
cache: function( owner ) {// Check if the owner object already has a cachevar value = owner[ this.expando ];// If not, create oneif ( !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 assignmentif ( owner.nodeType ) { owner[ this.expando ] = value;//即document[jQuery3210080552146542722581]={}// 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, this.expando, {value: value,configurable: true} );}}}return value; },
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
//即document[jQuery3210080552146542722581]
},
当有多个事件同时委托给同一个对象时,如document对象,每个事件绑定时的参数将存储在同一个document[expando]的value中,如下图中的click对象
事件触发时,将获取到内存中所有的事件参数 ,进行选择器比对,然后执行对应的回调函数。
总结
on事件处理函数,主要是通过jquery中的一个内部对象dataPriv(如下图)来实现事件绑定时和事件触发时的数据流通,当多个子对象事件委托给同一个父级对象Root时,程序将根据事先存储在内存中的数据进行选择器匹配,然后执行对应的回调函数,接着事件会继续冒泡传给当前对象的父级,若父级也绑定了相同类型的事件,则触发父级绑定的回调函数,以此类推...直到最上层Root为止(确切地说,在一次子级点击事件触发时,子级和父级的回调函数依次按冒泡顺序执行;事件只触发了一次,而不是多次)。
以上为研究jquery源码时对on事件绑定的一些总结,平常只会使用而没有研究过底层是如何实现的,写样例跟踪代码的过程中发现了不少以前没搞懂的细节,在此记录一下。
转载于:https://www.cnblogs.com/Youngly/p/8781645.html
jQuery源码解析之on事件绑定相关推荐
- jQuery源码解析(架构与依赖模块)
jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1. ...
- jQuery源码解析(架构与依赖模块)第一章 理解架构
1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, F ...
- jquery源码解析:代码结构分析
本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94) 定义了一些变量和函数, jQuery = function() ...
- JQuery 源码解析资料
2019独角兽企业重金招聘Python工程师标准>>> jQuery源码分析系列目录 jQuery源码解读-理解架构 jQuery源码解析(架构与依赖模块) jQuery v1.10 ...
- jquery源码解析:jQuery数据缓存机制详解2
上一课主要讲了jQuery中的缓存机制Data构造方法的源码解析,这一课主要讲jQuery是如何利用Data对象实现有关缓存机制的静态方法和实例方法的.我们接下来,来看这几个静态方法和实例方法的源码解 ...
- Android View体系(五)从源码解析View的事件分发机制
Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Sc ...
- jQuery 源码解析一:jQuery 类库整体架构设计解析
如果是做 web 的话,相信都要对 Dom 进行增删查改,那大家都或多或少接触到过 jQuery 类库,其最大特色就是强大的选择器,让开发者脱离原生 JS 一大堆 getElementById.get ...
- 浅谈jquery源码解析
本文主要是针对jquery v3.x版本来描述的,将从以下几个方面谈谈我对jquery的认识, 总体架构 $与$.fn jQuery.fn.init (重要) jQuery.extend 与jQ ...
- jQuery源码解析(2)—— Callback、Deferred异步编程
闲话 这篇文章,一个月前就该出炉了.跳票的原因,是因为好奇标准的promise/A+规范,于是学习了es6的promise,由于兴趣,又完整的学习了<ECMAScript 6入门>. 本文 ...
最新文章
- 机器学习入门--进阶资料和流程建议
- Kotlin 第三讲——集合篇1
- 前端学习(1701):前端系列javascript之闭包
- CV《神经风格转换》
- Spring中的@Value注解详解
- JAVA通过调用数据库函数调用存储过程
- 服务器64位还是32位系统好,云服务器64位还是32位
- 罗技Ghub配置文件压枪编程——仅供学习
- 能力风暴智能机器人编程实例与vjc4.2的相关问题
- 适用mac微信用户的免登陆多开防撤回插件-WeChatTweak
- 2022年面试工具篇Jmeter接口面试题及答案
- 简述计算机硬盘常见故障及处理方法,常见计算机硬盘故障的解决方法
- linux扩展模式触摸屏,Ubuntu14.04下使用触摸屏以及笔记本扩展触摸屏设置方法
- 网易有道三季报解读:转型“有道”,但依旧道阻且长
- 包邮再送500份!我们自制了一张【数据分析知识鼠标垫】,抓紧领取
- java七牛云图片压缩_七牛云 CDN 历史图片批量压缩
- 【即点即改】关于PHP即点即改的一些东西
- 多伦多大学计算机ib成绩要求,QS世界Top30大学 A-level及IB 成绩详细要求!
- [阿发你好]C/C++学习指南
- 直播回顾 | 第四期直播课堂:5G消息在工业领域的应用分享