zoukankan      html  css  js  c++  java
  • 【JQuery源码】事件绑定

      事件绑定的方式有很多种。使用了jQuery那么原来那种绑定方式(elem.click = function(){...})就不推荐了,原因?

      最主要的一个原因是elem.click = fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果。

      

      看一下jQuery绑定事件的方式有哪些

    jQuery.fn.eventType([[data,] fn])

      比如eventType指的是事件类型,比如click: $("#chua").click(fn);

      data这个参数一般都不会使用。这种方式事件绑定在("#chua")上,没有委托事件,和js原生的事件绑定更接近。我们看一下源码

    复制代码
    jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
            "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
            "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
          //合并15种事件统一增加到jQuery.fn上,内部调用this.on / this.trigger
          jQuery.fn[ name ] = function( data, fn ) {
          return arguments.length > 0 ?
          this.on( name, null, data, fn ) :
          //如果不带参数表示立刻触发指定事件
          this.trigger( name );
          };
    });
    复制代码

    jQuery.fn.bind( types[, data], fn )

      比如$("#chua").bind("click",fn)。直接将事件绑定到$("#chua")上,没有委托事件。源码

    bind: function( types, data, fn ) {
        return this.on( types, null, data, fn );
    },
    unbind: function( types, fn ) {
        return this.off( types, null, fn );
    }

    jQuery.fn.delegate(selector, types[, data], fn)

      顾名思义delegate这个函数是用来做事件委托的,将选择器selector对应的响应处理委托给当前jQuery所匹配的元素。

      比如:$(document).delegate('#big',"click",dohander);分析到这里顺便分析一下事件委托的处理流程

      当点击"#big"元素的时候,事件click会冒泡直到document节点;

      document绑定了处理事件,这个处理事件会调用到事件分发器dispatch;

      dispatch中取出对应事件类型click的所有的委托事件列表handlers;

      根据事件源event.target过滤出委托事件列表handlers中每一个元素的selector属性对应的节点处于事件原和委托节点document之间(包括事件源)的委托事件,保存为handlerQueue;

      执行handlerQueue里面的事件处理。

      上面是一个大致的流程,后续会详细分析。先看delegate源码

    复制代码
    delegate: function( selector, types, data, fn ) {
        return this.on( types, selector, data, fn );
    },
    undelegate: function( selector, types, fn ) {
        // ( namespace ) or ( selector, types [, fn] )
        return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
    }
    复制代码

    jQuery.fn.one( types[, selector[, data]], fn )

      通过one()函数绑定的事件处理函数都是一次性的,只有首次触发事件时会执行该事件处理函数。触发之后,jQuery就会移除当前事件绑定。

      比如$("#chua").one("click",fn);为#chua节点绑定一次性的click事件

      $(document).one("click","#chua",fn);将#chua的click事件委托给document处理。源码

    one: function( types, selector, data, fn ) {
            return this.on( types, selector, data, fn, 1 );
    }

    jQuery.fn.trigger(type[, data])

    jQuery.fn.triggerHandler(type[, data])

      trigger触发jQuery对象所匹配的每一个元素对应type类型的事件。比如$("#chua").trigger("click");

      triggeHandler只触发jQuery对象所匹配的元素中的第一个元素对应的type类型的事件,且不会触发事件的默认行为。

    复制代码
    //立刻触发jQuery对象内所有元素的指定type的事件
    trigger: function( type, data ) {
        return this.each(function() {
            jQuery.event.trigger( type, data, this );
        });
    },
    //立刻触发jQuery对象内第一个元素的指定type的事件,且不会触发事件(比如表单提交)的默认行为
    triggerHandler: function( type, data ) {
        var elem = this[0];
        if ( elem ) {
            return jQuery.event.trigger( type, data, elem, true );
        }
    }
    复制代码

      

      上面分析了那么些个事件绑定,有么有发现他们都是使用.on方式绑定的?这也是为什么提倡统一使用on来绑定的原因(one方式除外)。

    jQuery.fn.on( types[, selector[, data]], fn )

      .on的事件绑定一半的代码都实在处理传递不同参数的处理,这也是jQuery的口号Write less, do more的代价吧。最终使用jQuery.event.add来绑定事件。

      jQuery.event.add绑定事件有几个比较关键的地方:

      第一个,使用内部缓存来保存节点elem的事件信息

    复制代码
                //获取缓存数据 
           elemData = jQuery._data( elem );        ...
           
           //设置缓存数据 if ( !(events = elemData.events) ) { events = elemData.events = {}; } if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { ... }; //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露 eventHandle.elem = elem; }
    复制代码

      第二个,设置绑定事件信息,特别是指定的选择器selector、响应处理handler、响应事件类型type、命名空间namespace

    复制代码
            // handleObj:设置绑定事件信息。贯穿整个事件处理
            handleObj = jQuery.extend({
                type: type,
                origType: origType,
                data: data,
                handler: handler,
                guid: handler.guid,
                selector: selector,
                // For use in libraries implementing .is(). We use this for POS matching in `select`
                //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\(" +
                //whitespace + "*((?:-\d)?\d*)" + whitespace + "*\)|)(?=[^-]|$)", "i" )
                //用来判断亲密关系
                needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
                namespace: namespaces.join(".")
            }, handleObjIn );
    复制代码

      第三个,节点的事件列表中,真正的委托事件列表放置在前面,和delegateCount属性同步,即events.click.length假设为3,events.click.delegateCount假设为2。那么events.click[0]和events.click[1]所指定事件是委托事件。第三个events.click[2]对应的事件不是委托事件,而是节点自身的事件。

            //将事件对象handleObj添加到元素的处理列表,委托事件放在前面,委托代理计数递增
            if ( selector ) {
                handlers.splice( handlers.delegateCount++, 0, handleObj );
            } else {
                handlers.push( handleObj );
            }

      源码和添加事件后的结构上一章已经分析,详情请点击查看

      

      绑定有一个公用函数jQuery.fn.on。解绑同样有一个公用函数jQuery.fn.off

    jQuery.fn.off([ types[, selector][, fn]] )

      这里的传参有个比较特殊的情况:当types是浏览器事件对象event的时候,表示要去掉(解绑)委托节点上event.selector指定的委托事件

    复制代码
    //传入的参数是事件且绑定了处理函数
    if ( types && types.preventDefault && types.handleObj ) {
            // ( event )  dispatched jQuery.Event
            handleObj = types.handleObj;
            //types.delegateTarget是事件托管对象
            jQuery( types.delegateTarget ).off(
                //组合jQuery识别的type
                handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                handleObj.selector,
                handleObj.handler
                );
            return this;
    }
    复制代码

      无论如何最终都是调用jQuery.event.remove函数来解绑事件。

      jQuery.fn.off完整的源码如下

     View Code

      

      接下来分析一下事件解绑的低级api jQuery.event.remove。

    jQuery.event.remove

      jQuery使用.off()函数伤处绑定的事件时内部调用的基础函数是jQuery.event.remove。该函数的处理流程如下

      1. 分解传入的要删除的事件类型types,遍历类型,如果要删除的事件没有事件名,只有命名空间则表示删除该命名空间下所有绑定事件

    复制代码
    //分解types为type.namespace为单位元素的数组
    types = ( types || "" ).match( core_rnotwhite ) || [""];
    t = types.length;
    while ( t-- ) {
               tmp = rtypenamespace.exec( types[t] ) || [];
               type = origType = tmp[1];
               namespaces = ( tmp[2] || "" ).split( "." ).sort(); 
    
               //解绑当前元素的当前命名空间(types[ t ])上所有的事件
               if ( !type ) {
                  for ( type in events ) {
                     jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
                  }
                   continue;
               }
               ...
    复制代码

      2. 遍历类型过程中,删除匹配的事件,代理计数修正

    复制代码
    type = ( selector ? special.delegateType : special.bindType ) || type;
    handlers = events[ type ] || [];
    tmp = tmp[2] && new RegExp( "(^|\.)" + namespaces.join("\.(?:.*\.|)") + "(\.|$)" );
    
    //删除匹配事件
    origCount = j = handlers.length;
    while ( j-- ) {
               handleObj = handlers[ j ]; 
    
               //各种满足移除事件的条件才能移除
               if ( ( mappedTypes || origType === handleObj.origType ) &&
                   ( !handler || handler.guid === handleObj.guid ) &&
                   ( !tmp || tmp.test( handleObj.namespace ) ) &&
                   ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
                  handlers.splice( j, 1 ); 
            if ( handleObj.selector ) {
              handlers.delegateCount--;
            }
            if ( special.remove ) {
              special.remove.call( elem, handleObj );
            }
               }
    }
    复制代码

      3. 如果节点上指定类型的事件处理器已经为空,则将events上的该类型的事件处理对象移除

    复制代码
    // 移除事件处理对象
    // (移除特殊事件处理过程中避免潜在的无限递归,下一章会专门详解这种情况)
    if ( origCount && !handlers.length ) {
    //例如 var js_obj = document.createElement("div"); js_obj.onclick = function(){ …}
    //上面的js_obj是一个DOM元素的引用,DOM元素它长期在网页当中,不会消失,而这个DOM元素的一属性onclick,又是内部的函数引用(闭包),而这个匿名函数又和js_obj之间有隐藏的关联(作用域链)所以形成了一个,循环引用.
    if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
      jQuery.removeEvent( elem, type, elemData.handle );
    } 
    
    delete events[ type ];
    }
    复制代码

      4. 如果节点上没有任何绑定的事件,则清空事件处理入口handle

    if ( jQuery.isEmptyObject( events ) ) {
      delete elemData.handle;
      //removeData还检事件对象是否为空,所以使用它替代delete
      jQuery._removeData( elem, "events" );
    }

    拓展: 浏览器事件删除jQuery.removeEvent

    复制代码
    jQuery.removeEvent = document.removeEventListener ?
    function( elem, type, handle ) {
               if ( elem.removeEventListener ) {
                   elem.removeEventListener( type, handle, false );
               }
    } :
    function( elem, type, handle ) {
               var name = "on" + type;
               if ( elem.detachEvent ) {
                   // #8545, #7054,避免自定义事件在IE6-8中的内存泄露
                   // detachEvent需要传递第一个参数,不能是undefined的
                   if ( typeof elem[ name ] === core_strundefined ) {
                     elem[ name ] = null;
                   }
                   elem.detachEvent( name, handle );
               }
    };
    复制代码

     

  • 相关阅读:
    利用 chunked 类型响应实现后台请求的监听
    C/C++ 中的宏/Macro
    SSL/TLS 链接的建立/握手
    C/C++ 中 `printf` 格式化
    多媒体文件的容器与编解码器的关系
    Unix 开发中的 Make 三连
    shell 中长命令的换行处理
    C++ float vs double
    Xcode 中配置 clang-format 格式化 C++ 代码
    C++ `endl` 与 ` ` 的区别
  • 原文地址:https://www.cnblogs.com/shytong/p/5376338.html
Copyright © 2011-2022 走看看