jQuery源码解析之click()的事件绑定

栏目: jQuery · 发布时间: 6年前

内容简介:前言:这篇依旧长,请耐心看下去。

jQuery源码解析之click()的事件绑定

前言:

这篇依旧长,请耐心看下去。

一、事件委托

DOM有个 事件流 特性,所以触发DOM节点的时候,会经历3个阶段:

(1)阶段一:Capturing 事件捕获(从祖到目标)

事件 自上(document->html->body->xxx)而下到达目标节点的过程中,浏览器会检测 针对该事件的 监听器(用来捕获事件) ,并运行 捕获事件的监听器

(2)阶段二:Target 目标

浏览器找到监听器后,就运行该监听器

(3)阶段三:Bubbling 冒泡(目标到祖)

事件 自下而上(document->html->body->xxx)到达目标节点的过程中,浏览器会检测 不是 针对该事件的 监听器(用来捕获事件) ,并运行 非捕获事件的监听器

二、 $() .click()

作用:

为目标元素绑定点击事件

源码:

//这种写法还第一次见,将所有鼠标事件写成字符串再换成数组
  //再一一绑定到DOM节点上去
  //源码10969行
  jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
    "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
    "change select submit keydown keypress keyup contextmenu" ).split( " " ),
    function( i, name ) {
      //事件绑定
      // Handle event binding
      jQuery.fn[ name ] = function( data, fn ) {
        return arguments.length > 0 ?
          //如果有参数的话,就用jQuery的on绑定
          this.on( name, null, data, fn ) :
          //否则使用trigger
          this.trigger( name );
      };
    } );

解析:

可以看到,jQuery 将所有的鼠标事件都一一列举了出来,并通过 jQuery.fn[ name ] = function( data, fn ) { xxx }

如果有参数,则是绑定事件,调用 on() 方法;

没有参数,则是调用事件,调用 trigger() 方法( trigger() 放到下篇讲 )

三、 $() .on()

作用:

在被选元素及子元素上添加一个或多个事件处理程序

源码:

//绑定事件的方法
  //源码5812行
  jQuery.fn.extend( {
    //在被选元素及子元素上添加一个或多个事件处理程序
    //$().on('click',function()=<{})
    //源码5817行
    on: function( types, selector, data, fn ) {
      return on( this, types, selector, data, fn );
    },
    //xxx
    //xxx
})

最终调用的是 jQuery.on() 方法:

//绑定事件的on方法
  //源码5143行
  //目标元素,类型(click,mouseenter,focusin,xxx),回调函数function(){xxx}
  function on( elem, types, selector, data, fn, one ) {
    var origFn, type;
    //这边可以不看
    // Types can be a map of types/handlers
    if ( typeof types === "object" ) {

      // ( types-Object, selector, data )
      if ( typeof selector !== "string" ) {

        // ( types-Object, data )
        data = data || selector;
        selector = undefined;
      }
      for ( type in types ) {
        on( elem, type, selector, data, types[ type ], one );
      }
      return elem;
    }
    //直接调用$().on()的话会走这边
    if ( data == null && fn == null ) {

      // ( types, fn )
      //fn赋值为selector,即function(){}
      fn = selector;
      //再将selector置为undefined
      //注意这个写法,连等赋值
      data = selector = undefined;
    }
    //调用像$().click()的话会走这边
    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 elem;

    }
    //one()走这里
    if ( one === 1 ) {
      //将fn赋给origFn后,再定义fn
      origFn = fn;
      fn = function( event ) {
        //将绑定给目标元素的事件传给fn,
        //并通过$().off()卸载掉
        // Can use an empty set, since event contains the info
        jQuery().off( event );
        //在origFn运行一次的基础上,让origFn调用fn方法,arguments即event
        return origFn.apply( this, arguments );
      };
      //让fn和origFn使用相同的guid,这样就能移除origFn方法
      // Use same guid so caller can remove using origFn
      fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
    }

    return elem.each( function() {
      //最终调动$.event.add方法
      jQuery.event.add( this, types, fn, data, selector );

    } );
  }

解析:

可以看到,由于将 bind()、live() 和 delegate() 都合并进 on() 后,on() 里面的情况挺复杂的, data、selector、fn 相互赋值。

注意下 if ( one === 1 ) 这种情况,是 $().one()on() 里的具体实现,即调用一次 on() 后,就执行 jQuery().off( event ) ,卸载事件。

该方法最终调用 jQuery.event.add( ) 方法

四、jQuery.event.add( )

作用:

为目标元素添加事件

源码:

//源码5235行
  /*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
  jQuery.event = {

    global: {},
    //源码5241行
    //this, types, fn, data, selector
    add: function( elem, types, handler, data, selector ) {

      var handleObjIn, eventHandle, tmp,
        events, t, handleObj,
        special, handlers, type, namespaces, origType,
        //elemData正是目标元素jQuery中的id属性
        //初始值是{}
        elemData = dataPriv.get( elem );
      // Don't attach events to noData or text/comment nodes (but allow plain objects)
      if ( !elemData ) {
        return;
      }
      //调用者可以传入一个自定义数据对象来代替处理程序
      // Caller can pass in an object of custom data in lieu of the handler
      if ( handler.handler ) {
        handleObjIn = handler;
        handler = handleObjIn.handler;
        selector = handleObjIn.selector;
      }
      //确保不正确的选择器会抛出异常
      // Ensure that invalid selectors throw exceptions at attach time
      // Evaluate against documentElement in case elem is a non-element node (e.g., document)
      if ( selector ) {
        jQuery.find.matchesSelector( documentElement, selector );
      }

      //确保handler有唯一的id
      // Make sure that the handler has a unique ID, used to find/remove it later
      if ( !handler.guid ) {
        handler.guid = jQuery.guid++;
      }
      //如果事件处理没有,则置为空对象
      // Init the element's event structure and main handler, if this is the first
      //在这里,就应经给events赋值了,
      // 注意这种写法:赋值的同时,判断
      if ( !( events = elemData.events ) ) {
        events = elemData.events = {};
      }

      if ( !( eventHandle = elemData.handle ) ) {
        eventHandle = elemData.handle = function( e ) {

          //当在一个页面卸载后调用事件时,取消jQuery.event.trigger()的第二个事件
          // Discard the second event of a jQuery.event.trigger() and
          // when an event is called after a page has unloaded

          //jQuery.event.triggered: undefined
          //e.type: click/mouseout
          return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
            //让elem调用jQuery.event.dispatch方法,参数是arguments
            jQuery.event.dispatch.apply( elem, arguments ) : undefined;
        };
      }
      //通过空格将多个events分开,一般为一个,如click
      // Handle multiple events separated by a space
      types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
      t = types.length;
      while ( t-- ) {
        tmp = rtypenamespace.exec( types[ t ] ) || [];
        //click
        type = origType = tmp[ 1 ];
        //""
        namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
        // There *must* be a type, no attaching namespace-only handlers
        if ( !type ) {
          continue;
        }
        //如果event改变了它自己的type,就使用特殊的event handlers
        // If event changes its type, use the special event handlers for the changed type
        special = jQuery.event.special[ type ] || {};
        //如果选择器已定义,确定一个特殊event api的type
        //否则使用默认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会传递给所有的event handlers
        // handleObj is passed to all event handlers
        handleObj = jQuery.extend( {
          type: type,
          origType: origType,
          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
        if ( !( handlers = events[ type ] ) ) {
          handlers = events[ type ] = [];
          handlers.delegateCount = 0;

          // Only use addEventListener if the special events handler returns false
          if ( !special.setup ||
            special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
            //目标元素有addEventListener的话,调用绑定click事件
            if ( elem.addEventListener ) {
              elem.addEventListener( type, eventHandle );
            }
          }
        }
        //special的add/handleObj.handler.guidd的初始化处理
        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 );
        }
        //一旦有绑定事件,全局通知
        // Keep track of which events have ever been used, for event optimization
        jQuery.event.global[ type ] = true;
      }

    },

...
...

}

解析:

可以看到,很多的 if 判断,都是在初始化对象,最后通过 while 循环,调用目标元素的 addEventListener 事件,也就是说,click()/on() 的本质是 element.addEventListener() 事件,前面一系列的铺垫,都是在为目标 jQuery 对象添加必要的属性。

注意写法 if ( !( events = elemData.events ) ) ,在赋值的同时,判断条件

(1)dataPriv

//取唯一id
  //源码4361行
  var dataPriv = new Data();

在 jQuery 对象中,有唯一id的属性

$("#one")

jQuery源码解析之click()的事件绑定

elemData = dataPriv.get( elem )

jQuery源码解析之click()的事件绑定

① Data()

//目标元素的jQuery id
  //源码4209行
  function Data() {
    this.expando = jQuery.expando + Data.uid++;
  }

② jQuery.expando

jQuery.extend( {
    //相当于jQuery为每一个元素取唯一的id
    ///\D/g : 去掉非数字的字符
    // Unique for each copy of jQuery on the page
    //源码360行
    expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
    ...  
    ...
})

③ Math.random()

伪随机,到小数点后16位

expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

可以看到 jQuery 的 id 是由 jQuery + 版本号+ Math.random() 生成的

关于 Math.random() 是如何生成伪随机数的请看: https://www.zhihu.com/question/22818104

(2)rtypenamespace

var
    rkeyEvent = /^key/,
    rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
    //事件类型的命名空间
    //举例:var arr1 = "click.aaa.bbb".match(rtypenamespace);
    //console.log(arr1);//["click.aaa.bbb", "click", "aaa.bbb", index: 0, input: "click.aaa.bbb"]
    //源码5131行
    rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

综上,绑定事件的本质即调用 element.addEventListener() 方法,但 jQuery 有太多的情况需要考虑了。

jQuery源码解析之click()的事件绑定

(完)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Websites with Joomla!

Building Websites with Joomla!

H Graf / Packt Publishing / 2006-01-20 / USD 44.99

This book is a fast paced tutorial to creating a website using Joomla!. If you've never used Joomla!, or even any web content management system before, then this book will walk you through each step i......一起来看看 《Building Websites with Joomla!》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具