jQuery源码解析之jQuery.event.dispatch()

2019-06-13 admin

一、起源 jQuery.event.add()方法最终是用addEventListener绑定事件的:

elem.addEventListener( type, eventHandle )

eventHandle方法正是等于jQuery.event.dispatch()

  if ( !( eventHandle = elemData.handle ) ) {
        eventHandle = elemData.handle = function( e ) {
          return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
            jQuery.event.dispatch.apply( elem, arguments ) : undefined;
        };
   }

二、`<div class=“article fmt article__content” data-id=“1190000019464028” data-license="".event.dispatch() 作用: 触发绑定的事件的处理程序

源码:

    //源码5472行
    //nativeEvent即原生MouseEvent
    //触发事件的处理程序
    dispatch: function( nativeEvent ) {
      //修正event对象
      // Make a writable jQuery.Event from the native event object
      var event = jQuery.event.fix( nativeEvent );
      console.log(event,'event5479')

      var i, j, ret, matched, handleObj, handlerQueue,
        args = new Array( arguments.length ),
        //获取click事件的处理程序集合,结构如下:
        //[
        // {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
        // {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
        // delegateCount:0,
        //]
        //从数据缓存中获取事件处理集合
        handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
        //click:{
        // trigger:{},
        // _default:{}
        //}
        special = jQuery.event.special[ event.type ] || {};
      // Use the fix-ed jQuery.Event rather than the (read-only) native event
      args[ 0 ] = event;

      for ( i = 1; i < arguments.length; i++ ) {
        args[ i ] = arguments[ i ];
      }
      //this即目标元素
      //delegateTarget:委托目标
      event.delegateTarget = this;
      //这段代码压根不会执行,因为全局搜索没找到preDispatch
      // 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
      //结构如下
      //[{
      // elem:xx,
      // handlers:[
      //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
      //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
      //  ]
      //}]
      //获取handler队列
      handlerQueue = jQuery.event.handlers.call( this, event, handlers );
      // Run delegates first; they may want to stop propagation beneath us
      i = 0;
      //没有执行stopPropagation()的话
      console.log(handlerQueue,'handlerQueue5525')
      //先判断有没有冒泡
      //再判断有没有阻止剩下的handler执行
      while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
        console.log(matched,'matched5542')
        event.currentTarget = matched.elem;

        j = 0;
        //handleObj即单个事件处理程序
        //没有执行stopImmediatePropagation()的话

        //依次执行每一个handler
        while ( ( handleObj = matched.handlers[ j++ ] ) &&
        !event.isImmediatePropagationStopped() ) {

          // Triggered event must either 1) have no namespace, or 2) have namespace(s)
          // a subset or equal to those in the bound event (both can have no namespace).
          if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
            //通过循环将为event添加handleObj和handleObj.data
            event.handleObj = handleObj;
            event.data = handleObj.data;
            //关键代码,执行事件处理程序handler
            ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
              handleObj.handler ).apply( matched.elem, args );
            if ( ret !== undefined ) {
              //event.result赋值ret
              if ( ( event.result = ret ) === false ) {
                //阻止默认行为
                event.preventDefault();
                //阻止冒泡
                event.stopPropagation();
              }
            }
          }
        }
      }

      // Call the postDispatch hook for the mapped type
      if ( special.postDispatch ) {
        special.postDispatch.call( this, event );
      }
      console.log(handlers,'event5587')
      //undefined
      return event.result;
    },

解析:

(1)jQuery.event.fix() 作用: 将原生事件对象MouseEvent修正(fix)成jQueryevent对象

源码:

    //源码5700行
    fix: function( originalEvent ) {
      //如果存在属性id则原样返回(因为已处理成jQueryEvent)
      return originalEvent[ jQuery.expando ] ?
        originalEvent :
        new jQuery.Event( originalEvent );
    },

解析: 可以看到fix的本质是新建一个event对象,再看jQuery.Event()方法

(2)jQuery.Event() 源码:

 //click,false
  //修正event对象
  //源码5777行
  //src即MouseEvent
  jQuery.Event = function( src, props ) {

    // Allow instantiation without the 'new' keyword
    if ( !( this instanceof jQuery.Event ) ) {
      return new jQuery.Event( src, props );
    }

    // Event object
    //src.type=click
    if ( src && src.type ) {
      //MouseEvent
      this.originalEvent = src;
      //click
      this.type = src.type;

      // Events bubbling up the document may have been marked as prevented
      // by a handler lower down the tree; reflect the correct value.
      this.isDefaultPrevented = src.defaultPrevented ||
      src.defaultPrevented === undefined &&

      // Support: Android <=2.3 only
      src.returnValue === false ?
        returnTrue :
        returnFalse;

      // Create target properties
      // Support: Safari <=6 - 7 only
      // Target should not be a text node (#504, #13143)
      this.target = ( src.target && src.target.nodeType === 3 ) ?
        src.target.parentNode :
        src.target;

      this.currentTarget = src.currentTarget;
      this.relatedTarget = src.relatedTarget;

      // Event type
    } else {
      //click
      this.type = src;
    }

    // Put explicitly provided properties onto the event object
    //false
    if ( props ) {
      jQuery.extend( this, props );
    }

    // Create a timestamp if incoming event doesn't have one
    this.timeStamp = src && src.timeStamp || Date.now();

    // Mark it as fixed
    //修正的标志
    this[ jQuery.expando ] = true;
  };

解析: 简单来说,就是把原生event事件上的常用属性赋值到了jQueryevent

  $("#A").on("click" ,function (event) {
    //这个就是jQuery.Event()构建出的event
    console.log(event,"A被点击了")
  })

jQueryevent结构如下:

//click的event就是jQuery.Event
jQuery.Event{
  handleObj{
    data:undefined,
    guid: 2,
    handler:function(){console.log("A被点击了")},
    namespace: "clickA",
    origType: "click",
    selector: "#B",
    type: "click.clickA",
  },
  originalEvent:{
    //就是MouseEvent
  },
  target:div#B,
  type: "click",
  delegateTarget: div#A,
  //fix 的标志
  jQuery331087940272164138: true,
  currentTarget: div#A,
  isDefaultPrevented:xxx,
  timeStamp:Date.now(),
  isDefaultPrevented:function(){return false}
}

注意下originalEventjQuery.extend( this, props ) 前者就是原生MouseEvent,只是将原生event作为jQuery.event的originalEvent属性了; 后者是扩展属性,如果开发者想额外加入自定义属性的话。

(3)dataPriv.get( this, "events" ) 注意: jQuery的数据缓存里的events和上面说的event是不同的

数据缓存的events是用来结构如下:

{
  click:[
    {
      type: "click", 
      origType: "click", 
      data: undefined, 
      handler: function(){console.log("B委托A绑定click事件")}, 
      guid: 1,
      namespace: "",
      needsContext: undefined,
      selector: #B,
    },
    {
      type: "click", 
      origType: "click", 
      data: undefined, 
      handler: function(){console.log("A绑定click事件")}, 
      guid: 2,
      namespace: "",
      needsContext: undefined,
      selector: undefined,
    },
    //事件委托的数量
    delegateCount:1,
  ],
  focus:[
    {
      type: "focus", 
      origType: "focus", 
      data: undefined, 
      handler: function(){console.log("A绑定focus事件")}, 
      guid: 3,
      namespace: "",
      needsContext: undefined,
      selector: undefined,
    },
     delegateCount:0,
  ],
}

(4) jQuery.event.handlers 作用: 获取handler队列

源码:

jQuery.event = {
    //源码5547行
    //组装事件处理队列  
    //event是fix过的MouseEvent, handlers  
    handlers: function( event, handlers ) {
      var i, handleObj, sel, matchedHandlers, matchedSelectors,
        handlerQueue = [],
        //0
        delegateCount = handlers.delegateCount,
        //目标元素
        cur = event.target;
      //handlers,第一个handler是委托事件,第二个handler是自身事件
      // 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 ) ) {
        //循环,event.target冒泡到cur.parentNode,
        //直至绑定的目标元素#A,退出循环
        for ( ; cur !== this; cur = cur.parentNode || this ) {
          console.log(cur,'cur5618')
          // 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 = {};
            //在每一层,依次将委托的事件push进matchedHandlers
            //顺序由下到上
            for ( i = 0; i < delegateCount; i++ ) {
              handleObj = handlers[ i ];
              //sel就是#C
              // 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()和jQuery().find()是不一样的
                  jQuery.find( sel, this, null, [ cur ] ).length;
              }
              if ( matchedSelectors[ sel ] ) {
                matchedHandlers.push( handleObj );
              }
            }
            //然后将该层委托事件的数组放进handlers中
            //handlerQueue是所有层委托事件的集合
            if ( matchedHandlers.length ) {
              handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
            }
          }
        }
      }

      // Add the remaining (directly-bound) handlers
      //最终冒泡到this元素
      cur = this;
      //1<2
      //将除委托事件的事件(如自身绑定的事件)放入handlerQueue中
      if ( delegateCount < handlers.length ) {
        handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
      }
      //[{
      // elem:xx,
      // handlers:[
      //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
      //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
      //  ]
      //}]
      return handlerQueue;
    },

}

解析: 注意下这个双层循环,目的是把每一层的委托事件的集合pushmatchedHandlers,然后再将matchedHandlers放进handlerQueue队列

在处理完每层的委托事件后,将剩下的自身绑定事件再pushhandlerQueue队列中

也就是说,handlerQueue的结构如下:

[
//委托事件
  {
   elem:xx,
   handlers:[
      {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
      {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
    ]
  },
//自身绑定事件
  {
   elem:xxx,
   handlers:[
      {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 3},
      {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 4},
    ]
  },
]

(5)回过头再往下看dispatch 源码,是两个while循环,举个例子来说明下:

<div id="A" style="background-color: deeppink">
  这是A

  <div id="B" style="background-color: bisque">
    这是B
  </div>

</div>

  $("#A").on("click" ,function (event) {
    console.log(event,"A被点击了")
  })

  $("#A").on("click" ,"#B",function (event) {
    console.log(event,"点击了B,即B委托A的click事件被点击了")
  })

那么会 先循环并执行委托事件, 即handler=function (event) {console.log(event,"点击了B,即B委托A的click事件被点击了")}, 再循环并执行目标元素自身绑定事件, 即handler=function (event) {console.log(event,"A被点击了")} 前提是冒泡不被阻止

最后,执行click事件的事件处理程序的关键代码如下:

handleObj.handler.apply( matched.elem, args )

(完)

[转载]原文链接:https://segmentfault.com/a/1190000019464028

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-67170.html

文章标题:jQuery源码解析之jQuery.event.dispatch()

相关文章
JavaScript正则进阶之路——活学妙用奇淫正则表达式
有些童鞋肯定有所疑惑,花了大量时间学习正则表达式,却发现没有用武之地,正则不就是验证个邮箱嘛,其他地方基本用不上,其实,大部分人都是这种感觉,所以有些人干脆不学,觉得又难又没多大用处。殊不知,想要成为编程大牛,正则表达式必须玩转,GitH...
2017-05-31
ajax教程之ajax使用Http请求
ajax中是如何让使用http请求的呢? 在传统的JS编程中,如果您希望从服务器上的文件或数据库中得到任何的信息,或者向服务器发送信息的话,就必须利用一个 HTML 表单向服务器 GET 或 POST 数据。而用户则需要单击“提交”按钮来发...
2015-11-12
bootstrap table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)代码分享
1.bootstrap-table 单击单行选中 $(&#x27;#gzrwTable&#x27;).on(&#x27;click-row.bs.table&#x27;, function(e, row, $element) { $(&#x...
2017-02-17
jQuery给多个不同元素添加class样式的方法
本文实例讲述了jQuery给多个不同元素添加class样式的方法。分享给大家供大家参考。具体分析如下: jQuery可以通过addClass()方法给多个不同的html元素同时添加相同的class &lt;!DOCTYPE html&gt;...
2017-03-22
JS教程之基础
javascript教程之什么是 JavaScript? JavaScript 被设计用来向 HTML 页面添加交互行为。JavaScript 是一种脚本语言(脚本语言是一种轻量级的编程语言)。JavaScript 由数行可执行计算机代码组...
2015-11-12
DOM之通俗易懂讲解
DOM 是所有前端开发每天打交道的东西,但是随着 jQuery 等库的出现,大大简化了 DOM 操作,导致大家慢慢的 “遗忘” 了它的本来面貌。不过,要想深入学习前端知识,对 DOM 的了解是不可或缺的,所以本文力图系统的讲解下 DOM 的...
2016-01-13
Vue.js原理分析之observer模块详解
介绍 observer是Vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。 **注意:**本文是针对Vue@2.1.8进行分析 observer模块在Vue项目中的代码位置是src/c...
2017-03-16
Ajax教程之Ajax介绍
Ajax 由 HTML、JavaScript™ 技术、DHTML 和 DOM 组成,这一杰出的方法可以将笨拙的 Web 界面转化成交互性的 Ajax 应用程序。本文的作者是一位 Ajax 专家,他演示了这些技术如何协同工作 —— 从总体概述...
2015-11-12
数据格式之战:JSON vs XML
在比较JSON和XML之前,我们先来上一堂关于数据格式的简要历史(更准确的说,是关于XML的始祖): 早在1970年,IBM开发了一种叫Generalized Markup Language的标记语言,简称GML,它主要是为脚本语言定义的一...
2016-01-13
JavaScript深入之类数组对象与
类数组对象 所谓的类数组对象: 拥有一个 length 属性和若干索引属性的对象 举个例子: var array = [&#x27;name&#x27;, &#x27;age&#x27;, &#x27;sex&#x27;]; var ...
2017-05-27
回到顶部