首页 web前端 js教程 jQuery 1.9.1源码分析系列(十)事件系统之主动触发事件和模拟冒泡处理_jquery

jQuery 1.9.1源码分析系列(十)事件系统之主动触发事件和模拟冒泡处理_jquery

May 16, 2016 pm 03:30 PM

发现一个小点,先前没有注意的

   stopPropagation: function() {
      var e = this.originalEvent;
      ...
      if ( e.stopPropagation ) {
        e.stopPropagation();
      }
登录后复制

  jQuery重载stopPropagation函数调用的本地事件对象的stopPropagation函数阻止冒泡。也就是说,阻止冒泡的是当前节点,而不是事件源。

  说到触发事件,我们第一反应是使用$(...).click()这种方式触发click事件。这种方式毫无疑问简洁明了,如果能使用这种方式推荐使用这种方式。但是如果是自定义事件呢?比如定义一个$(document).on("chuaClick","#middle",fn);这种情况怎么触发事件?这就要用到$("#middle").trigger("chuaClick")了。

a.触发事件低级API——jQuery.event.trigger

  trigger函数对所有类型事件的触发提供了支持。这些事件主要分为两类:普通浏览器事件(包含带有命名空间的事件如"click.chua")、自定义事件。因为要统一处理,所以函数内部实现没有调用.click()这种方式来对普通浏览器事件做捷径处理,而是统一流程。处理过程如下

  1.获取要触发的事件(传入的event可能是事件类型而不是事件对象)

event = event[ jQuery.expando ] ? event :new jQuery.Event( type, typeof event === "object" && event );
登录后复制

  2.修正浏览器事件(主要有修正事件源)和组合正确的事件处理参数data

  if ( type.indexOf(".") >= 0 ) {
        //有命名空间的事件触发; 先取出事件处理入口函数handle()使用的事件类型type
        namespaces = type.split(".");
        type = namespaces.shift();
        namespaces.sort();
      }
      ...// 调用者可以传递jQuery.Event对象,普通对象,甚至是字符串
      event = event[ jQuery.expando ] ?
      event :
      new jQuery.Event( type, typeof event === "object" && event );
      event.isTrigger = true;
      event.namespace = namespaces.join(".");
      event.namespace_re = event.namespace ?
      new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
      null;
      // 重置result属性,避免上次的结果残留
      event.result = undefined;
      if ( !event.target ) {
        event.target = elem;
      }
      // 克隆传参data并将event放在传参data的前面,创建出事件处理入口函数的参数列表,创建后结果可能是[event,data]
      data = data == null ?
      [ event ] :
      jQuery.makeArray( data, [ event ] );
登录后复制

  后面这段组合事件处理参数列表data在后面处理时调用

  if ( handle ) {
          handle.apply( cur, data );
        }
登录后复制

  3.判断是否是特殊节点对象的的特殊事件,是的话特殊处理

 special = jQuery.event.special[ type ] || {};
  if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
    return;
  }
登录后复制

  这里面需要特殊处理的事件比较少,这里列一下

 special: {
      click.trigger: function(){   // checkbox, 触发本地事件确保状态正确if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
            this.click();
            return false;
          }
      },
      focus.trigger: function() {  // 触发本地事件保证失焦/聚焦序列正确if ( this !== document.activeElement && this.focus ) {
            try {
              this.focus();
              return false;
            } catch ( e ) {
              // Support: IE<9
              // If we error on focus to hidden element (#1486, #12518),
              // let .trigger() run the handlers
            }
          }
      },
      blur.trigger: function() {if ( this === document.activeElement && this.blur ) {
            this.blur();
            return false;
          }
      }
    }
登录后复制

  4.从事件源开始遍历父节点直到Window对象,将经过的节点保存(保存到eventPath)下来备用

for ( ; cur; cur = cur.parentNode ) {
  eventPath.push( cur );
  tmp = cur;
}
// 将window也压入eventPath(e.g., 不是普通对象也不是断开连接的DOM)
if ( tmp === (elem.ownerDocument || document) ) {
  eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
登录后复制

  5.循环先前保存的节点,访问节点缓存,如果有对应的事件类型处理队列则取出其绑定的事件(入口函数)进行调用。      

// jQuery绑定函数处理:判断节点缓存中是否保存相应的事件处理函数,如果有则执行
       handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
       if ( handle ) {
           handle.apply( cur, data );
       }
       // 本地绑定处理
       handle = ontype && cur[ ontype ];
       if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
           event.preventDefault();
       }
登录后复制

  6. 最后处理浏览器默认事件,比如submit标签的提交表单处理。

  // 如果没有人阻止默认的处理,执行之
      if ( !onlyHandlers && !event.isDefaultPrevented() ) {
        ...
      }
登录后复制

  注意:普通事件加上命名空间仍然属于普通事件,普通调用方式依然其作用。比如$(document).on('click.chua',"#id",fn1).on("click","#id",fn2);当点击“#id”节点的时候fn1依然会被调用。触发指定命名空间事件的唯一方式是trigger:$("#id").trigger("click.chua"),此时只会调用fn1。

  从第4、5个步骤可以看到trigger的另外一个巨大作用——模拟冒泡处理。后面会分析到

b. 事件特殊处理jQuery.event.special(主要有事件替代、模拟冒泡)详解

  委托设计是基于事件可冒泡的。但是有些事件是不可冒泡的,有的事件在不同的浏览器上支持的冒泡情况不同。还有不同的浏览器支持的事件类型也不尽相同。这些处理主要都被放在jQuery.event.special中。jQuery.event.special对象中保存着为适配特定事件所需的变量和方法。

  具体有:

delegateType / bindType (用于事件类型的调整)
setup (在某一种事件第一次绑定时调用)
add (在事件绑定时调用)
remove (在解除事件绑定时调用)
teardown (在所有事件绑定都被解除时调用)
trigger (在内部trigger事件的时候调用)
noBubble
_default
handle (在实际触发事件时调用)
preDispatch (在实际触发事件前调用)
postDispatch (在实际触发事件后调用)

  看一下模拟冒泡的函数simulate

simulate: function( type, elem, event, bubble ) {
      // 构建一个新的事件以区别先前绑定的事件.
      // 新构建的事件避免阻止冒泡, 但如果模拟事件可以阻止默认操作的话,我们做同样的阻止默认操作。
      var e = jQuery.extend(
        new jQuery.Event(),
        event,
        { type: type,
          isSimulated: true,
          originalEvent: {}
        }
        );
      if ( bubble ) {
        jQuery.event.trigger( e, null, elem );
      } else {
        jQuery.event.dispatch.call( elem, e );
      }
      if ( e.isDefaultPrevented() ) {
        event.preventDefault();
      }
    }
登录后复制

  看到没有,真正模拟冒泡函数是jQuery.event.trigger函数

special第一组

  这里面涉及到冒泡处理的问题。

special: {
  load: {
    //阻止触发image.load事件冒泡到window.load
    noBubble: true
  },
  click: {
    //checkbox触发时保证状态正确
    trigger: function() {if (...) {this.click();return false;}}
  },
  focus: {
    //触发本当前节点blur/focus事件 确保队列正确
    trigger: function() {
      if ( this !== document.activeElement && this.focus ) {
        try {
          this.focus();
          return false;
        } catch ( e ) {
          // IE<9,如果我们错误的让隐藏的节点获取焦点(#1486, #12518),
          // 让.trigger()运行处理器
        }
      }
    },
    delegateType: "focusin"
  },
  blur: {
    trigger: function() {
      if ( this === document.activeElement && this.blur ) {
        this.blur();
        return false;
      }
    },
    delegateType: "focusout"
  },
  beforeunload: {
    postDispatch: function( event ) {
      //即使的returnValue等于undefined,Firefox仍然会显示警告 
      if ( event.result !== undefined ) {
        event.originalEvent.returnValue = event.result;
      }
    }
  }
}
登录后复制

  focus/blur本来是不冒泡的,但是我们依然可以通过$(document).on('focus ','#left',fn)来绑定,是怎么做到的?我们来看jQuery的处理

  第一步,将focus绑定的事件转化为focusin来绑定,focusin在W3C的标准中是冒泡的,除开火狐之外的浏览器也确实支持冒泡(火狐浏览器focusin/focusout支持冒泡的兼容后面会详解)

type = ( selector ? special.delegateType : special.bindType ) || type;

  然后,根据新得到的type类型(focusin)获取新的special

special = jQuery.event.special[ type ] || {};  

获取的special结果为

jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
  var attaches = 0,
  handler = function( event ) {
    //模拟冒泡
    jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
  };
  jQuery.event.special[ fix ] = {
        setup: function() {
          if ( attaches++ === 0 ) {
            document.addEventListener( orig, handler, true );
          }
        },
        teardown: function() {
          if ( --attaches === 0 ) {
            document.removeEventListener( orig, handler, true );
          }
        }
  };
});
登录后复制

  再然后,就是绑定事件,绑定事件实际上就对focusin、focusout做了兼容处理,源码中第一个判断有special.setup.call(…)这段代码,根据上面setup函数可见第一次进入的时候实际上是通过setup函数中的document.addEventListener( orig, handler, true )绑定事件,注意:第一个参数是是orig,因为火狐不支持focusin/focusout所以jQuery使用focus/blur替代来监听事件;注意第三个参数是true,表示在事件捕获阶段触发事件。

  我们知道任何浏览器捕获都是从外层到精确的节点的,所有的focusin事件都会被捕获到,然后执行handler函数(里面是jQuery.event.simulate函数,源码略)。其他事件绑定则进入if分支将事件直接绑定到elem上

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
  if ( elem.addEventListener ) {
    elem.addEventListener( type, eventHandle, false ); 
  } else if ( elem.attachEvent ) {
    elem.attachEvent( "on" + type, eventHandle );
  }
}

登录后复制

special第二组:mouseenter/mouseleave

//使用mouseover/out和事件时机检测创建mouseenter/leave事件
jQuery.each({
  mouseenter: "mouseover",
  mouseleave: "mouseout"
  }, function( orig, fix ) {
    jQuery.event.special[ orig ] = {
      delegateType: fix,
      bindType: fix,
      handle: function( event ) {
        var ret,
        target = this,
        related = event.relatedTarget,
        handleObj = event.handleObj;
        //对于mousenter/leave,当related在target外面的时候才调用handler
        //参考: 当鼠标离开/进入浏览器窗口的时候是没有relatedTarget的
        if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
          event.type = handleObj.origType;
          ret = handleObj.handler.apply( this, arguments );
          event.type = fix;
        }
        return ret;
      }
    };
});
登录后复制

  需要注意的是只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件。对应mouseleave这样的话,mouseenter子元素不会反复触发事件,否则在IE中经常有闪烁情况发生

  使用mouseover/out和事件时机检测创建mouseenter/leave事件有个关键的判断

if ( !related || (related !== target && !jQuery.contains( target, related )) )
登录后复制

  其中!jQuery.contains( target, related )表示related在target外面。我们使用图例来解释

  我们假设处理的是mouseenter事件,进入target。

  鼠标从related到target,很明显related在target外面,所以当鼠标移动到target的时候满足条件,调用处理。

  

  现在反过来,很明显related在target里面,那么鼠标之前就处于mouseenter状态(意味着之前就进行了mouseenter处理器处理),避免重复调用当然是不进行任何处理直接返回了。

  

  我们假设处理的是mouseleave事件,离开target。

  鼠标从target到related,很明显related在target里面,所以当鼠标移动到related的时候依然么有离开target,不做处理。

  

  鼠标从target到related,很明显related在target外面,所以当鼠标移动到related的时候已经离开了target的范围,做处理。

  

special第三组:submit和change

主要是ie下submit不能冒泡的处理

  jQuery.event.special.submit主要有一下几个特征

  setup
  postDispatch
  teardown

  根据添加事件的代码可知添加事件的时候如果符合条件则会调用setup来添加事件

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false )

  jQuery在ie下模拟submit事件以click和keypress替代,只不过是添加了命名空间来区别和普通click和keypress事件。

setup: function() {
  ...
  jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
    var elem = e.target,
    form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) &#63; elem.form : undefined;
      if ( form && !jQuery._data( form, "submitBubbles" ) ) {
        jQuery.event.add( form, "submit._submit", function( event ) {
          event._submit_bubble = true;
        });
        jQuery._data( form, "submitBubbles", true );
      }
  });
},
登录后复制

  在事件调用过程中(dispatch)会调用postDispatch来处理

if ( special.postDispatch ) {
    special.postDispatch.call( this, event );
}
  postDispatch中调用simulate完成事件处理
postDispatch: function( event ) {
  // If form was submitted by the user, bubble the event up the tree
  if ( event._submit_bubble ) {
    delete event._submit_bubble;
    if ( this.parentNode && !event.isTrigger ) {
      jQuery.event.simulate( "submit", this.parentNode, event, true );
    }
  }
},
登录后复制

  teardown用在删除事件绑定中

  ie下change事件的处理和submit类似,事件使用beforeactivate替代来监听,处理函数变成了handle,在事件分发(dispatch)中执行代码

ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
    .apply( matched.elem, args ); 
登录后复制

主要源码如下

 jQuery.event.special.change = {
      setup: function() {
        //rformElems = /^(&#63;:input|select|textarea)$/i
        if ( rformElems.test( this.nodeName ) ) {
          // IE不会在check/radio失焦前触发change事件; 在属性更改后触发它的click事件
          // 在special.change.handle中会吞掉失焦触发的change事件.
          // 这里任然会在check/radio失焦后触发onchange事件.
          if ( this.type === "checkbox" || this.type === "radio" ) {
            jQuery.event.add( this, "propertychange._change", function( event ) {
              if ( event.originalEvent.propertyName === "checked" ) {
                this._just_changed = true;
              }
            });
            jQuery.event.add( this, "click._change", function( event ) {
              if ( this._just_changed && !event.isTrigger ) {
                this._just_changed = false;
              }
              // Allow triggered, simulated change events (#11500)
              jQuery.event.simulate( "change", this, event, true );
            });
          }
          return false;
        }
        // 事件代理; 懒惰模式为后代input节点添加change事件处理
        jQuery.event.add( this, "beforeactivate._change", function( e ) {
          var elem = e.target;
          if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
            jQuery.event.add( elem, "change._change", function( event ) {
              if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
                jQuery.event.simulate( "change", this.parentNode, event, true );
              }
            });
            jQuery._data( elem, "changeBubbles", true );
          }
        });
      },
      handle: function( event ) {
        var elem = event.target;
        // 吞掉本地单选框和复选框的change事件,我们在上面已经出发了事件
        if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
          return event.handleObj.handler.apply( this, arguments );
        }
      },
    }
登录后复制

  OK,到此,事件系统也告一个段落了,谢谢大家多多支持。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

仓库:如何复兴队友
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

在JavaScript中替换字符串字符 在JavaScript中替换字符串字符 Mar 11, 2025 am 12:07 AM

JavaScript字符串替换方法详解及常见问题解答 本文将探讨两种在JavaScript中替换字符串字符的方法:在JavaScript代码内部替换和在网页HTML内部替换。 在JavaScript代码内部替换字符串 最直接的方法是使用replace()方法: str = str.replace("find","replace"); 该方法仅替换第一个匹配项。要替换所有匹配项,需使用正则表达式并添加全局标志g: str = str.replace(/fi

自定义Google搜索API设置教程 自定义Google搜索API设置教程 Mar 04, 2025 am 01:06 AM

本教程向您展示了如何将自定义的Google搜索API集成到您的博客或网站中,提供了比标准WordPress主题搜索功能更精致的搜索体验。 令人惊讶的是简单!您将能够将搜索限制为Y

8令人惊叹的jQuery页面布局插件 8令人惊叹的jQuery页面布局插件 Mar 06, 2025 am 12:48 AM

利用轻松的网页布局:8个基本插件 jQuery大大简化了网页布局。 本文重点介绍了简化该过程的八个功能强大的JQuery插件,对于手动网站创建特别有用

构建您自己的Ajax Web应用程序 构建您自己的Ajax Web应用程序 Mar 09, 2025 am 12:11 AM

因此,在这里,您准备好了解所有称为Ajax的东西。但是,到底是什么? AJAX一词是指用于创建动态,交互式Web内容的一系列宽松的技术。 Ajax一词,最初由Jesse J创造

什么是这个&#x27;在JavaScript? 什么是这个&#x27;在JavaScript? Mar 04, 2025 am 01:15 AM

核心要点 JavaScript 中的 this 通常指代“拥有”该方法的对象,但具体取决于函数的调用方式。 没有当前对象时,this 指代全局对象。在 Web 浏览器中,它由 window 表示。 调用函数时,this 保持全局对象;但调用对象构造函数或其任何方法时,this 指代对象的实例。 可以使用 call()、apply() 和 bind() 等方法更改 this 的上下文。这些方法使用给定的 this 值和参数调用函数。 JavaScript 是一门优秀的编程语言。几年前,这句话可

通过来源查看器提高您的jQuery知识 通过来源查看器提高您的jQuery知识 Mar 05, 2025 am 12:54 AM

jQuery是一个很棒的JavaScript框架。但是,与任何图书馆一样,有时有必要在引擎盖下发现发生了什么。也许是因为您正在追踪一个错误,或者只是对jQuery如何实现特定UI感到好奇

10张移动秘籍用于移动开发 10张移动秘籍用于移动开发 Mar 05, 2025 am 12:43 AM

该帖子编写了有用的作弊表,参考指南,快速食谱以及用于Android,BlackBerry和iPhone应用程序开发的代码片段。 没有开发人员应该没有他们! 触摸手势参考指南(PDF) Desig的宝贵资源

如何创建和发布自己的JavaScript库? 如何创建和发布自己的JavaScript库? Mar 18, 2025 pm 03:12 PM

文章讨论了创建,发布和维护JavaScript库,专注于计划,开发,测试,文档和促销策略。

See all articles