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:[{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 handlers
        if ( 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) handlers
        cur = this;
        if ( delegateCount < handlers.length ) {
            handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
        }

        return handlerQueue;
    },
27
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 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;//即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]={handle:f(),events:{      click:[{type: "click", origType: "click", data: undefined, guid: 1, handler: ?(),selector:"#div",namespace:"",needContext:false}},
      delegateCount:1]}}

  },

 

当有多个事件同时委托给同一个对象时,如document对象,每个事件绑定时的参数将存储在同一个document[expando]的value中,如下图中的click对象

事件触发时,将获取到内存中所有的事件参数 ,进行选择器比对,然后执行对应的回调函数。

以上为研究jquery源码时对on事件绑定的一些总结,平常只会使用而没有研究过底层是如何实现的,写样例跟踪代码的过程中发现了不少以前没搞懂的细节,在此记录一下。

原文地址:https://www.cnblogs.com/Youngly/p/8781645.html

时间: 2024-10-13 07:56:06

jQuery源码解析之on事件绑定的相关文章

二.jQuery源码解析之构建jQuery之构建函数jQuery的7种用法

一:$(selectorStr[,限制范围]),接受一个选择器(符合jQuery规范的字符串),返回一个jQuery对象;二:$(htmlStr[,文档对象]),$(html[,json对象])传入html字符串,创建一个新的dom元素 三:$(dom元素),$(dom元素集合)将dom元素转换成jQuery对象.四:$(自定义对象)封装普通对象为jQuery对象.五:$(回调函数)绑定ready事件监听函数,当Dom加载完成时执行.六:$(jQuery对象)接受一个jQuery对象,返回一个j

jQuery源码解析(架构与依赖模块)第一章 理解架构

1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器.jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用).events.实现动画效果,并且方便地为网站提供AJAX交互.jQuery还有一个比较大的优势是,它

jquery源码解析:代码结构分析

本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function(){}; (96,283)   给jQuery对象添加一些属性和方法(实例方法,通过$("div")这类的jQuery实例对象来调用) (285,347)   extend : jQuery的继承方法 (349,817)   jQuery.extend():扩展一些工具方法(静态方法,直接通

五.jQuery源码解析之jQuery.extend(),jQuery.fn.extend()

给jQuery做过扩展或者制作过jQuery插件的人这两个方法东西可能不陌生.jQuery.extend([deep],target,object1,,object2...[objectN]) jQuery.fn.extend([deep],target,object1,,object2...[objectN])这两个属性都是用于合并两个或多个对象的属性到target对象.deep是布尔值,表示是否进行深度合并,默认是false,不执行深度合并.通过这种方式可以在jQuery或jQuery.fn

十三.jQuery源码解析之$.type()

512行:出现了一个class2type. 在jQuery中全局搜索这个变量. 这段代码的意思是将一串字符串通过空格分割成数组,并且使用each遍历数组来初始化class2type. 最终的结果应该是这样的. { "[object Array]":"array", "[object Boolean]":"boolean", "[object Date]":"date", "[o

四.jQuery源码解析之jQuery.fn.init()的参数解析

从return new jQuery.fn.init( selector, context, rootjQuery )中可以看出 参数selector和context是来自我们在调用jQuery方法时传过来的.那么selector和context都有哪些可能. 对于表格中的4~9行中的可能做具体分析. 如果selector是字符串,则首先检测是html代码还是#id. 126行的if语句:以"<"开头,以">"结尾,且长度>=3.则假设额这个是HT

八.jQuery源码解析之get()

理论上get是用来返回jQuery对象中部分或全部元素为数组的,但是转换为数组后,数组中的单个元素又是一个一个dom元素.所以get还有另外一个功效,就是将jQuery对象转换成dom对象.如果get中的参数为空了,则直接调用toArray();如果参数小于0,则表示从元素后面开始计算,如果num>0表示直接返回指定位置的元素. 八.jQuery源码解析之get(),布布扣,bubuko.com

十七.jQuery源码解析之入口方法Sizzle(1)

函数Sizzle(selector,context,results,seed)用于查找与选择器表达式selector匹配的元素集合.该函数是选择器引擎的入口. 函数Sizzle执行的6个关键步骤如下: 1.解析选择器表达式,解析出块表达式和关系符. 2.如果存在位置伪类,则从左向右查找: a.查找第一个块表达式匹配的元素集合,得到第一个上下文元素集合. b.遍历剩余的块表达式和块间关系符,不断缩小上下文元素集合. 3.否则从右向左查找: a.查找最后一个块表达式匹配的元素集合,得到候选集,映射集

十五.jQuery源码解析之Sizzle总体结构.htm

Sizzle是一款纯javascript实现的css选择器引擎,它具有完全独立,无库依赖;小;易于扩展和兼容性好等特点. W3C Selectors API规范定义了方法querySelector()和querySelectorAll(),但是IE6,7不支持这两个方法. 在Sizzele内部,如果浏览器支持方法querySelectorAll(),则调用该方法查找元素,如果不支持,则模拟该方法的行为. Sizzle支持几乎所有的css3选择器,并且会按照文档位置返回结果. 上面截取的只是Siz