资讯专栏INFORMATION COLUMN

jQuery 源码系列(十四)自定义事件

elliott_hu / 1238人阅读

摘要:不过也有自己的一套自定义事件方案。可以和事件拿来对比,他们都是用来模拟和执行监听的事件。冒泡事件就是就是由内向外冒泡的过程,这个过程不是很复杂。参考解密事件核心自定义设计三解密事件核心模拟事件四本文在上的源码地址,欢迎来。

欢迎来我的专栏查看系列文章。

以前,我只知道,只有当对浏览器中的元素进行点击的时候,才会出发 click 事件,其它的事件也一样,需要人为的鼠标操作。

后来随着学习的不断深入,才知道原来 JS 可以写函数来控制事件的执行,这样子写代码才有意思。记得很久很久以前一些恶意网站,明明鼠标没有点击,却被网站强行的点击了某个链接,大概实现的方式就是这样的吧。

原生事件

其实 JS 的原生事件已经做得挺好了,只是 jQuery 将其进行封装,做的更好。

关于 document.createEvent,下面是一个简单的事件点击的例子:

var fn = function(){
  console.log("click");
}

var button = document.getElementById("#id");
button.addEventListener("click", fn);

// 点击事件 MouseEvent
var myClick = document.createEvent("MouseEvent");
myClick.initMouseEvent("click", false, false, null);

// 执行
button.dispatchEvent(myClick); // "click"

除了鼠标事件,还可以自定义事件:

// 随便自定义一个事件 test.click
button.addEventListener("test.click", fn);

var testEvent = document.createEvent("CustomEvent");
// customEvent 也可以初始化为鼠标事件,不一定非要自定义事件
testEvent.initCustomEvent("test.click", false, false, null);

button.dispatchEvent(testEvent); // "click"

JS 原生的模拟事件,使用起来还是很方便的,以上便是原生的。

不过 jQuery 也有自己的一套自定义事件方案。

jQuery.trigger

jQuery.trigger 可以和 HTMLElement.dispatchEvent 事件拿来对比,他们都是用来模拟和执行监听的事件。

如何使用

关于使用,则比较简单了.trigger():

var $body = $(document.body);

// 先绑定事件
$body.on("click", function(){
  console.log("click");
})

// 执行
$body.trigger("click"); //"click"

trigger 还支持更多的参数,同样可以自定义事件:

$body.on("click.test", function(e, data1, data2){
  console.log(data1 + "-" + data2);
})

$body.trigger("click.test", ["hello", "world"]);
trigger 源码

trigger 的源码有些简单,因为还是要借助于 jQuery.event 来处理:

jQuery.fn.extend( {
  trigger: function(type, data){
    return this.each(function(){
      jQuery.event.trigger(type, data, this);
    })
  },
  // triggerHandler 处理第一个且不触发默认事件
  triggerHandler: function( type, data ) {
    var elem = this[ 0 ];
    if ( elem ) {
      return jQuery.event.trigger( type, data, elem, true );
    }
  }
} );

所以 trigger 事件的起点又回到了 jQuery.event

jQuery.event.trigger

其实 trigger 和 add + handler 函数很类似,大致都是从 data cache 中搜索缓存,执行回调函数。需要考虑要不要执行默认事件,即第四个参数为 true 的情况。

jQuery.extend(jQuery.event, {
  // onleyHandlers 表示不考虑冒泡事件
  trigger: function( event, data, elem, onlyHandlers ) {

    var i, cur, tmp, bubbleType, ontype, handle, special,
      eventPath = [ elem || document ],
      type = hasOwn.call( event, "type" ) ? event.type : event,
      namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

    cur = tmp = elem = elem || document;

    // Don"t do events on text and comment nodes
    if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
      return;
    }

    // 异步不冲突
    if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
      return;
    }

    if ( type.indexOf( "." ) > -1 ) {

      // Namespaced trigger; create a regexp to match event type in handle()
      namespaces = type.split( "." );
      type = namespaces.shift();
      namespaces.sort();
    }
    ontype = type.indexOf( ":" ) < 0 && "on" + type;

    // 改装原生的 event 事件
    event = event[ jQuery.expando ] ?
      event :
      new jQuery.Event( type, typeof event === "object" && event );

    // 判断是否只执行当前 trigger 事件,不冒泡
    event.isTrigger = onlyHandlers ? 2 : 3;
    event.namespace = namespaces.join( "." );
    event.rnamespace = event.namespace ?
      new RegExp( "(^|.)" + namespaces.join( ".(?:.*.|)" ) + "(.|$)" ) :
      null;

    // Clean up the event in case it is being reused
    event.result = undefined;
    if ( !event.target ) {
      event.target = elem;
    }

    // Clone any incoming data and prepend the event, creating the handler arg list
    data = data == null ?
      [ event ] :
      jQuery.makeArray( data, [ event ] );

    // Allow special events to draw outside the lines
    special = jQuery.event.special[ type ] || {};
    if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
      return;
    }

    // 向 document 冒泡并把冒泡结果存储到 eventPath 数组中
    if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

      bubbleType = special.delegateType || type;
      if ( !rfocusMorph.test( bubbleType + type ) ) {
        cur = cur.parentNode;
      }
      for ( ; cur; cur = cur.parentNode ) {
        eventPath.push( cur );
        tmp = cur;
      }

      // Only add window if we got to document (e.g., not plain obj or detached DOM)
      if ( tmp === ( elem.ownerDocument || document ) ) {
        eventPath.push( tmp.defaultView || tmp.parentWindow || window );
      }
    }

    // 按需求来执行
    i = 0;
    while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {

      event.type = i > 1 ?
        bubbleType :
        special.bindType || type;

      // 从 data cache 中获得回调函数
      handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
        dataPriv.get( cur, "handle" );
      if ( handle ) {
        // 执行
        handle.apply( cur, data );
      }

      // Native handler
      handle = ontype && cur[ ontype ];
      if ( handle && handle.apply && acceptData( cur ) ) {
        event.result = handle.apply( cur, data );
        if ( event.result === false ) {
          event.preventDefault();
        }
      }
    }
    event.type = type;

    // If nobody prevented the default action, do it now
    if ( !onlyHandlers && !event.isDefaultPrevented() ) {

      if ( ( !special._default ||
        special._default.apply( eventPath.pop(), data ) === false ) &&
        acceptData( elem ) ) {

        // Call a native DOM method on the target with the same name as the event.
        // Don"t do default actions on window, that"s where global variables be (#6170)
        if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {

          // Don"t re-trigger an onFOO event when we call its FOO() method
          tmp = elem[ ontype ];

          if ( tmp ) {
            elem[ ontype ] = null;
          }

          // Prevent re-triggering of the same event, since we already bubbled it above
          jQuery.event.triggered = type;
          elem[ type ]();
          jQuery.event.triggered = undefined;

          if ( tmp ) {
            elem[ ontype ] = tmp;
          }
        }
      }
    }

    return event.result;
  },
})
总结

jQuery.event.trigger 中,比较有意思的是模拟冒泡机制,大致的思路就是:

把当前 elem 存入数组;

查找当前 elem 的父元素,如果符合,push 到数组中,重复第一步,否则下一步;

遍历数组,从 data cache 中查看是否绑定 type 事件,然后依次执行。

冒泡事件就是就是由内向外冒泡的过程,这个过程不是很复杂。

event 事件应该就这么多内容吧。

参考

解密jQuery事件核心 - 自定义设计(三)
MDN createEvent
解密jQuery事件核心 - 模拟事件(四)

本文在 github 上的源码地址,欢迎来 star。

欢迎来我的博客交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86831.html

相关文章

  • JS或Jquery

    摘要:大潮来袭前端开发能做些什么去年谷歌和火狐针对提出了的标准,顾名思义,即的体验方式,我们可以戴着头显享受沉浸式的网页,新的标准让我们可以使用语言来开发。 VR 大潮来袭 --- 前端开发能做些什么 去年谷歌和火狐针对 WebVR 提出了 WebVR API 的标准,顾名思义,WebVR 即 web + VR 的体验方式,我们可以戴着头显享受沉浸式的网页,新的 API 标准让我们可以使用 ...

    CatalpaFlat 评论0 收藏0
  • JavaScript专题系列文章

    摘要:专题系列共计篇,主要研究日常开发中一些功能点的实现,比如防抖节流去重类型判断拷贝最值扁平柯里递归乱序排序等,特点是研究专题之函数组合专题系列第十六篇,讲解函数组合,并且使用柯里化和函数组合实现模式需求我们需要写一个函数,输入,返回。 JavaScript 专题之从零实现 jQuery 的 extend JavaScritp 专题系列第七篇,讲解如何从零实现一个 jQuery 的 ext...

    Maxiye 评论0 收藏0
  • webpack多页应用架构系列十四):No复制粘贴!多项目共用基础设施

    摘要:原文地址如果您对本系列文章感兴趣,欢迎关注订阅这里前言本文介绍如何在多项目间共用同一套基础设施,又或是某种层次的框架。而以上所述的种种,就构成了一套完整的解决方案,也称基础设施。下面就以从到的改造过程来介绍如何实现多项目共用基础设施。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/1190...

    cyrils 评论0 收藏0
  • webpack多页应用架构系列(十):如何打造一个定义的bootstrap

    摘要:我个人惯用的是,因此本文以为例来介绍如何打造一个自定义的。引入全局的方法请看我之前的这篇文章多页应用架构系列四老式插件还不能丢,怎么兼容,我的脚手架项目也是使用的这套方案。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/1190000007043716如果您对本系列文章感兴趣,欢迎关注订阅...

    jindong 评论0 收藏0
  • [系统安全] 三十五.Procmon工具基本用法及文件进程、注册表查看

    摘要:本文将分享软件基本用法及文件进程注册表查看,这是一款微软推荐的系统监视工具,功能非常强大可用来检测恶意软件。可以帮助使用者对系统中的任何文件注册表操作进行监视和记录,通过注册表和文件读写的变化,有效帮助诊断系统故障或发现恶意软件病毒及木马。 ...

    kk_miles 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<