相关文章推荐
冲动的香槟  ·  南山大事紀 - 南山中學·  7 月前    · 
温文尔雅的竹笋  ·  申台龙接手国足新帅,能否打破困境迎来新生?_ ...·  9 月前    · 
闯红灯的雪糕  ·  金奖数量创历史新高,学校再获优秀组织奖!-创 ...·  1 年前    · 
温柔的楼梯  ·  思想文库_中国人民大学哲学院·  1 年前    · 
仗义的长颈鹿  ·  【华为 B3 ...·  1 年前    · 
Code  ›  jQuery源码解析之click()的事件绑定开发者社区
jquery 源码 handler jquery事件
https://cloud.tencent.com/developer/article/1965084
伤情的红豆
2 年前
作者头像
进击的小进进
0 篇文章

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

前往专栏
腾讯云
备案 控制台
开发者社区
学习
实践
活动
专区
工具
TVP
文章/答案/技术大牛
写文章
社区首页 > 专栏 > 前端干货和生活感悟 > 正文

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

发布 于 2022-03-28 15:03:12
592 0
举报

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

一、事件委托 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")
elemData = dataPriv.get( elem )

① 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/,
 
推荐文章
冲动的香槟  ·  南山大事紀 - 南山中學
7 月前
温文尔雅的竹笋  ·  申台龙接手国足新帅,能否打破困境迎来新生?_足球_中国_龙的
9 月前
闯红灯的雪糕  ·  金奖数量创历史新高,学校再获优秀组织奖!-创新创业学院
1 年前
温柔的楼梯  ·  思想文库_中国人民大学哲学院
1 年前
仗义的长颈鹿  ·  【华为 B3 智能手环使用总结】蓝牙连接|配置|APP|久座提醒|通知管理_摘要频道_什么值得买
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号