jquery事件核心源碼分析

     咱們從綁定事件開始,一步步往下看:html

     以jquery.1.8.3爲例,平時經過jquery綁定事件最經常使用的是on方法,大概分爲下面3種類型:node

$(target).on('click',function(){
    //函數內容    
})
$(target).on('click','.child',function(){
    //函數內容    
})
$(target).on({
    click:function(){},
    mouseover:function(){},
    mouseout:function(){}
})

    第一種是咱們最經常使用的寫法,經過元素選擇器,直接綁定事件;jquery

    第二種則是利用了事件委託原理,由最初的父元素代理子元素的事件,動態添加的元素綁定事件用第一種方法時無效的;windows

    第三種則是同一元素同時綁定多個事件時的簡略寫法。api

    咱們來看一下on方法的源碼,若是咱們想封裝插件相似on方法調用,能夠像on同樣來書寫,具體可見另外一篇文章jQuery插件開發(溢出滾動)數組

on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
        var origFn, type;

        // Types can be a map of types/handlers
        //上述第三種用法,傳入第一個參數爲object
        if ( typeof types === "object" ) {
            // ( types-Object, selector, data )
            if ( typeof selector !== "string" ) { // && selector != null
                // ( types-Object, data )
                data = data || selector;
                selector = undefined;
            }
            for ( type in types ) {
                this.on( type, selector, data, types[ type ], one );
            }
            return this;
        }

        if ( data == null && fn == null ) {
            // ( types, fn )
            fn = selector;
            data = selector = undefined;
        } 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 this;
        }

        if ( one === 1 ) {
            origFn = fn;
            fn = function( event ) {
                // Can use an empty set, since event contains the info
                jQuery().off( event );
                return origFn.apply( this, arguments );
            };
            // Use same guid so caller can remove using origFn
            fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
        }
        return this.each( function() {
            jQuery.event.add( this, types, fn, data, selector );
        });
    }

 

     咱們能夠看到,on方法內部的代碼相似於初始化,經過對傳入參數的分析,來矯正type,fn,data,selector等變量,從而正確的調用jquery.event.add方法。jquery.event是事件的核心。瀏覽器

    jquery.event 代碼結構以下:緩存

jQuery.event = {
       add:function(){
           
       },
       global:{},
       remove:function(){
           
       },
       customEvent:function(){
           
       },
       trigger:function(){
           
       },
       dispatch:function(){
          //在老版本的jquery,此方法名爲handle 
       },
       props:'',
       fixHooks:{
           
       },
       keyHooks    :{
           
       },
       mouseHooks:{
           
       },
       fix:function(){
           
       },
       special:function(){
           
           
       },
       simulate :function(){
           
           
       }
}

      其中add方法經過一些設置爲元素註冊添加事件:app

      所謂的特殊事件指相似於mouseenter,mouseleave,ready事件並非瀏覽器所支持的事件,他們不能經過統一的addEventListener/attachEvent來添加這個事件.而是經過setup和teardown來綁定和刪除事件,以下:函數

beforeunload: {
    setup: function( data, namespaces, eventHandle ) {
        // We only want to do this special case on windows
        if ( jQuery.isWindow( this ) ) {
            this.onbeforeunload = eventHandle;
        }
    },

    teardown: function( namespaces, eventHandle ) {
        if ( this.onbeforeunload === eventHandle ) {
            this.onbeforeunload = null;
        }
    }
}
add: function( elem, types, handler, data, selector ) {

  var elemData, eventHandle, events,
      t, tns, type, namespaces, handleObj,
      handleObjIn, handlers, special;

  // Don't attach events to noData or text/comment nodes (allow plain objects tho)
  if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
      return;
  }
  
  // Caller can pass in an object of custom data in lieu of the handler
  // 若是傳入的handler包括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
  //爲每一個元素添加一個惟一的guid
  if ( !handler.guid ) {
      handler.guid = jQuery.guid++;  
  }

  // Init the element's event structure and main handler, if this is the first
  //elemData結構見下面截圖
  events = elemData.events;
  if ( !events ) {
      elemData.events = events = {};  //初次綁定事件
  }
  eventHandle = elemData.handle;
  if ( !eventHandle ) {
      //eventHandle 通過dispatch處理,已不一樣於最初傳入的handler
      elemData.handle = eventHandle = function( e ) {
          // Discard the second event of a jQuery.event.trigger() and
          // when an event is called after a page has unloaded
          return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
              jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
              undefined;
      };
      // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
      eventHandle.elem = elem;
  }

  // Handle multiple events separated by a space
  // jQuery(...).bind("mouseover mouseout", fn);
  types = jQuery.trim( hoverHack(types) ).split( " " );
  for ( t = 0; t < types.length; t++ ) {  //相似 'click input keyUp'一次傳入多個事件

      tns = rtypenamespace.exec( types[t] ) || [];
      type = tns[1];
      namespaces = ( tns[2] || "" ).split( "." ).sort();

      // 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
      type = ( selector ? special.delegateType : special.bindType ) || type;

      // Update special based on newly reset type
      special = jQuery.event.special[ type ] || {};

      // handleObj is passed to all event handlers
      handleObj = jQuery.extend({
          type: type,
          origType: tns[1],
          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
      handlers = events[ type ];
      if ( !handlers ) {
          handlers = events[ type ] = [];
          handlers.delegateCount = 0;

          // Only use addEventListener/attachEvent if the special events handler returns false
          //若是爲非special事件則由addeventListener或attachEvent事件綁定,不然擇優special.setup綁定
          if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
              // Bind the global event handler to the element
              //當前eventHandle是通過處理的eventHandle
              if ( elem.addEventListener ) {
                  elem.addEventListener( type, eventHandle, false );

              } else if ( elem.attachEvent ) {
                  elem.attachEvent( "on" + type, eventHandle );
              }
          }
      }

      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 );
      }
      console.log(elemData)
      // Keep track of which events have ever been used, for event optimization
      jQuery.event.global[ type ] = true;
  }

  // Nullify elem to prevent memory leaks in IE
  elem = null;
},

        其中注意 elemData = jQuery._data( elem )  這句,咱們簡單綁定一個事件,而後看elemData值

$(document).click(function(){
    console.log(1)
})

 

     如上左圖,最終結果elemData即jquery的緩存數據中主要包含兩個屬性,events及handle,其中events包含了當前元素註冊的全部事件,如click,keydown等,其中每個事件下面又能夠包括多個handler,每一個handler有一個惟一的guid,後面觸發及刪除相應事件函數都要用到這個,events對象還包含一個屬性爲delegateCount,則記錄着該元素總共代理事件的次數。在右圖中能夠看到在某一個事件下綁定的不一樣handler,代理事件(selector部位undefined的狀況)排在前面,而綁定在元素自身的事件排在代理事件後面。

     須要注意的是代碼中的elem.addEventListener( type, eventHandle, false )並不一樣於咱們簡單的將handler處理函數綁定,而是對handler經過dispatch進行了處理。

     另外,在事件函數中,js默認傳入的第一個參數爲事件對象.

     下面咱們來看dispatch方法,該方法接受傳入的event參數,並對綁定在元素上的事件進行處理:例如咱們代碼以下

<div class='parent' style='width:900px;height:500px;background-color:#CCC'>
   <p class='child'>
     <a class='inner'>點擊</a>
   </p>
</div>
<script>
$('.parent').on('click',function(){
    
})
$('.parent').on('click','.child',function(){
    
})
$('.parent').on('click','.inner',function(){
    
})
</script>

     能夠看到,div元素自己綁定有click事件,同時又代理子元素p和a的事件,這樣當在div發生點擊事件的時候,第一步dispatch會從事件元素的currentTarget開始往上循環遍歷直到div元素,將須要觸發事件的元素及事件加到handlerQueue數組中 (前提是元素自己有代理事件),  而後會將綁定在元素自己的事件添加到handlerQueue。通過上面兩步的處理,handlerQueue就造成一個須要觸發事件的集合,經過這個集合,咱們便能正確的響應事件。

dispatch: function( event ) {
    // Make a writable jQuery.Event from the native event object
    // 經過fix方法對event進行兼容性處理
    event = jQuery.event.fix( event || window.event );

    var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
        handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
        delegateCount = handlers.delegateCount,
        args = core_slice.call( arguments ),
        run_all = !event.exclusive && !event.namespace,
        special = jQuery.event.special[ event.type ] || {},
        handlerQueue = [];

    // 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
    if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
        return;
    }

    // Determine handlers that should run if there are delegated events
    // Avoid non-left-click bubbling in Firefox (#3861)
    
    
    //火狐右鍵會觸發click事件,可是event.button值爲2
    //delegateCount不爲0表明元素自己有代理其餘元素事件
    if ( delegateCount && !(event.button && event.type === "click") ) {
        /*事件從event.target冒泡到當前元素
        #  例如元素自己綁定有事件a,並且代理其子元素child事件b及child子元素c事件,
        #  則點擊c元素時,執行事件順序爲c- b- a,即節點層次越深,事件執行優先級越高
        */
        for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {

            // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
            if ( cur.disabled !== true || event.type !== "click" ) {
                selMatch = {};
                matches = [];
                //代理事件,delegateCount爲代理事件的數量,不一樣handler事件的順序見上圖中右圖,代理事件在上,自身事件在下
                for ( i = 0; i < delegateCount; i++ ) {  
                    handleObj = handlers[ i ];
                    sel = handleObj.selector;
                    if ( selMatch[ sel ] === undefined ) {
                        selMatch[ sel ] = handleObj.needsContext ?
                            jQuery( sel, this ).index( cur ) >= 0 :
                            jQuery.find( sel, this, null, [ cur ] ).length;
                    }
                    if ( selMatch[ sel ] ) {
                        matches.push( handleObj );
                    }
                }
                if ( matches.length ) {
                    handlerQueue.push({ elem: cur, matches: matches });  //委託事件
                }
            }
        }
    }

    // Add the remaining (directly-bound) handlers
    if ( handlers.length > delegateCount ) {
        //自身事件
        handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
    }

    // Run delegates first; they may want to stop propagation beneath us
    // hangdlerQueue是一個集合元素自身事件及代理子元素事件的數組
    // 例如 html結構爲 <div><p><a></a></p></div>,當點擊範圍在p同時不在a內時,則會執行p和div的事件,
    // 相對應的handlerQuesu中並不包含a
    for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
        matched = handlerQueue[ i ];
        event.currentTarget = matched.elem;

        for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
            handleObj = matched.matches[ j ];

            // Triggered event must either 1) be non-exclusive and have no namespace, or
            // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
            if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {

                event.data = handleObj.data;
                event.handleObj = handleObj;
                ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
                        .apply( matched.elem, args );

                if ( ret !== undefined ) {
                    event.result = ret;
                    if ( ret === false ) {
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
        }
    }

    // Call the postDispatch hook for the mapped type
    if ( special.postDispatch ) {
        special.postDispatch.call( this, event );
    }

    return event.result;
},

     

      具體如上所示,源碼都作了相應備註,其中handlerQueue結構以下,前兩項爲代理事件,最後一項爲元素自己事件,matches爲當前元素handler集合。

 

      其中fix函數用於對事件對象的修正,首先構建一個新的可擴展的event對象,在jquery.event中還包含props,fixHooks,keyHooks,mouseHooks,分別存儲了事件對象的公共屬性,鍵盤事件屬性,鼠標事件屬性等,根據事件類型爲新構建event對象賦予新的屬性,同時咱們在後期擴展時也可爲該event對象賦予自定義屬性。

fix: function( event ) {
    if ( event[ jQuery.expando ] ) {
        return event;
    }

    // Create a writable copy of the event object and normalize some properties
    var i, prop,
        originalEvent = event,
        fixHook = jQuery.event.fixHooks[ event.type ] || {},
        copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

    event = jQuery.Event( originalEvent );

    for ( i = copy.length; i; ) {
        prop = copy[ --i ];
        event[ prop ] = originalEvent[ prop ];
    }

    // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
    if ( !event.target ) {
        event.target = originalEvent.srcElement || document;
    }

    // Target should not be a text node (#504, Safari)
    if ( event.target.nodeType === 3 ) {
        event.target = event.target.parentNode;
    }

    // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
    event.metaKey = !!event.metaKey;

    return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
},

    

     固然jquery.event還有trigger,remove,simulate等其餘方法,在此就不一一列舉,基本思路都是一致的。對以上原理理解透了,就能夠本身根據須要來擴展jquery方法,如mousewheel事件,咱們能夠利用fix方法來完成對event對象的擴展,而不用本身從新寫一套兼容性的代碼,具體下節再分析。

     文中若有錯誤及不當之處,請及時指出,謝謝! 

     文中所用jquery版本爲1.8.3。1.2.6版本的jquery事件核心更容易理解。固然裏面缺乏事件代理的處理。

相關文章
相關標籤/搜索