• jquery源码解析:jQuery工具方法Callbacks详解


    我们首先来讲下Callbacks是如何使用的:第一个例子

    function a(){}

    function b(){}

    var cb = $.Callbacks();

    cb.add(a);

    cb.add(b);

    cb.fire();      //就会先执行a方法,再执行b方法

    上面大概的意思是:add方法,就是把方法添加到数组list中,每执行一次,就添加一个方法。而fire方法,就是把list数组中的方法按顺序执行。

    使用场景:第二个例子

    function a(n){ }

    (function(){

      function b(n){}

    })

    在这里你无法调用b方法,因为b方法是局部变量。因此,你可以使用Callbacks来解决这个问题。

    function a(n){}

    var cb = $.Callbacks();

    cb.add(a);

    (function(){

      function b(n){}

      cb.add(b);

    })

    cb.fire("hello");

    这时,b方法即使是局部变量,也可以通过cb.fire()进行调用。而且fire中的参数"hello",也可以传入a,b方法。

    Callbacks 可以传4个值,jQuery.Callbacks(options),options有四个值:once,memory,unique,stopOnFalse 。

    在第一个例子中,假设我们调用两次cb.fire(); 那么第一次会执行a,b方法,第二次也会调用a,b方法。但是如果你定义cb时这样,var cb = $.Callbacks("once");那么只有第一次会执行a,b方法,第二次不会执行。once的意思是只执行list数组中的方法一次,之后你调用fire,不会执行list数组中的方法。

    memory的作用是:

    var cb = $.Callbacks();

    cb.add(a);

    cb.fire();

    cb.add(b);

    针对以上代码,方法a会执行,b方法不会执行。但是你在定义cb时,var cb = $.Callbacks("memory");这时,a,b方法都会执行。它具有记忆性。

    unique的作用:去重,add如果添加相同的方法,在没有unique的情况下,添加几个相同的方法,就会执行几次相同的方法,但是你传入了unique,不管你添加多少次相同的方法,它都只会执行一次。

    stopOnFalse的作用:

    function a(){   return false;}

    function b(){}

    var cb = $.Callbacks();

    cb.add(a);

    cb.add(b);

    cb.fire(); 

    方法a,b都会执行。但是如果你定义cb时,var cb = $.Callbacks("stopOnFalse");只会执行a方法。因为a方法返回了false,而stopOnFalse的作用就是当方法返回false时,就停止循环list。因此b方法就不会执行了。

    当然这四种参数,你可以组合使用,比如:var cb =  $.Callbacks("once memory");

    接下来,源码解析:由于代码比较多,因此给大家一个线索,$.Callbacks -> return self; -> self.add(a) -> list.push(a) -> self.add(b) -> list.push(b) -> self.fire() ->self.fireWith -> 私有方法fire() -> 循环list数组,执行里面的a,b方法。请按照此顺序看代码,然后再根据4个参数分别代表什么意思,再按顺序看一次。里面有详细的代码解释。

    var optionsCache = {};

    function createOptions( options ) {
      var object = optionsCache[ options ] = {};    //optionsCache[once] = {}
      jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {  

        //core_rnotwhite = /S+/g, 取字符,options.match( core_rnotwhite ) = ["once"],此正则是为了处理多个值时,比如"once match"变成["once","memory"]
        object[ flag ] = true;      //_是index值0,flag是value值"once"
      });
      return object;     //optionsCache[once] = { "once":true }
    }

    jQuery.Callbacks = function( options ) {    //options有四个值:once,memory,unique,stopOnFalse 

      options = typeof options === "string" ? ( optionsCache[ options ] || createOptions( options ) ) : jQuery.extend( {}, options );

      //当传入参数时,就是字符串,比如:once,那么就会先去optionsCache中取once属性,如果之前没有此值,就调用createOptions(once)方法

      var memory,
        fired,
          firing,
            firingStart,
              firingLength,
                firingIndex,
                  list = [],
                    stack = !options.once && [],  //如果有once,那么stack就是false,如果没有,那么stack就死[],if条件里面空数组为真,因此在fire()->fireWith()时,就可以再次执行list数组中的方法了。
                      fire = function( data ) {     //这里才是真正执行list数组中方法的地方
                        memory = options.memory && data;
                        fired = true;
                        firingIndex = firingStart || 0;
                        firingStart = 0;
                        firingLength = list.length;
                        firing = true;     //正在触发list数组中的方法
                        for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                          if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {

                          //如果回调方法返回false,并且有传入stopOnFalse参数,就会停止for循环,停止执行list数组中后面的方法
                            memory = false;
                            break;
                          }
                        }
                        firing = false;   //方法执行结束
                        if ( list ) {       //当list数组中的方法执行结束后,就会判断stack是否有值
                          if ( stack ) {
                            if ( stack.length ) {
                              fire( stack.shift() );   //如果有值,就会再次触发fire执行
                            }
                          } else if ( memory ) {   //如果有once,stack就是false,就会走else
                            list = [];     //如果有传入了memory,只会清空list数组。举个例子:var cb = $.Callbacks("once memory");cb.add(a),cb.fire();cb.add(b);cb.fire()。因为有once,所以只会执行一次,因此第二个fire没用。又由于有memory,所以a,b方法都会执行一次。如果这里没有memory,就会执行下面的else语句,这时只会执行a方法,b方法也不会执行。

                          } else {     
                            self.disable();   //阻止后面的所有fire操作
                          }
                        }
                      },
      self = {
        add: function() {    //此add方法,就是往list数组中push方法(fire时调用的方法)
          if ( list ) {       //[]为真
            var start = list.length;
            (function add( args ) {   
              jQuery.each( args, function( _, arg ) {
                var type = jQuery.type( arg );
                if ( type === "function" ) {    //如果是方法就push到list中
                  if ( !options.unique || !self.has( arg ) ) {   //如果有唯一判断,比如传入unique,就必须判断list数组中是否有此方法了,如果没有,才push到list中。self.has(arg)方法就是判断list数组中是否有arg方法。
                    list.push( arg );
                  }
                } else if ( arg && arg.length && type !== "string" ) {
                  add( arg );   //此时add([a,b])是立即执行方法add而不是self.add。
                }
              });
            })( arguments );     //这里处理add(a,b),add([a,b]),同时添加多个方法的情况
            if ( firing ) {
              firingLength = list.length;
            } else if ( memory ) {   //第一次调用add时,memory是undefined。当调用fire时,如果你传入了memory,则memory就会变成true,这时你再调用add,就会进入if语句,进行fire。所以你传入memory,fire后,再调用add(b),b方法会执行。
              firingStart = start;
              fire( memory );
            }
          }
          return this;
        },
        remove: function() {    //去掉list数组中相应的方法
          if ( list ) {
            jQuery.each( arguments, function( _, arg ) {
              var index;
              while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                list.splice( index, 1 );        //主要通过数组的splice方法去掉
                if ( firing ) {
                  if ( index <= firingLength ) {
                    firingLength--;
                  }
                  if ( index <= firingIndex ) {
                    firingIndex--;
                  }
                }
              }
            });
          }
          return this;
        },
        has: function( fn ) {   //jQuery.inArray( fn, list ),如果fn在list中就返回1,不在,就返回-1
          return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );   //如果不传入值,就判断list中是否有值,有值就返回true。
        },
        empty: function() {
          list = [];
          firingLength = 0;
          return this;
        },
        disable: function() {    //后面所有的操作都失效
          list = stack = memory = undefined;
          return this;
        },
        disabled: function() {  
          return !list;
        },
        lock: function() {  //只会锁住后面的fire操作,让cb.fire方法失效。
          stack = undefined;
          if ( !memory ) {
            self.disable();
          }
          return this;
        },
        locked: function() {
          return !stack;
        },
        fireWith: function( context, args ) {     //这里执行list数组中的方法
          if ( list && ( !fired || stack ) ) {   //第一次调用时fired是undefined,第二次调用时,fired为true,就要看stack了。
            args = args || [];        //如果fire中传了值,就是args,如果没传,就是空数组 
            args = [ context, args.slice ? args.slice() : args ];
            if ( firing ) {   //当for循环在执行list数组中的方法时,firing为真,这时调用fire()->fireWith的话,只会把args添加到stack中。当list数组中的方法执行完之后。就会根据stack中是否有值来进行处理。举个例子:var cb = $.Callbacks();function a(){ cb.fire()};funciton b(){};cb.add(a,b);cb.fire();执行a方法,这时又触发fire(),但是不会再次执行a方法,因为这时firing为true,所以只会把stack加1,而是先等b方法执行结束后,才会又重新执行a方法。私有fire方法中会判断stack的值,如果有值,就会继续循环list数组中的方法进行调用执行。
              stack.push( args );

            } else {
              fire( args );        //真正执行list数组中的方法是私有方法fire。
            }
          }
          return this;
        },
        fire: function() {       //fire方法其实就是执行list中的方法
          self.fireWith( this, arguments );   //它调用的是fireWith方法.arguments就是fire中传入的参数,它可以给list数组中的方法传值进去
          return this;
        },
        fired: function() {
          return !!fired;   //只要调用过一次fire就会返回true
        }
      };

      return self;   //返回的是self对象。当调用var cb = $.Callbacks(),cb =self。所以调用cb.add,fire,其实就是self.add和fire方法
    };

    加油!

  • 相关阅读:
    第十三章 类继承
    第十一章 使用类
    第十章 对象和类
    第九章 内存模型和名称空间
    第八章 函数幽探
    史上最详细得虚拟机安装过程,傻瓜式操作
    JVM内存结构图表展示
    主流消息队列rocketMq,rabbitMq比对使用
    springboot+druid+mybatis plus的多数据源配置
    linux服务器开放防火墙和端口,以及查询状态
  • 原文地址:https://www.cnblogs.com/chaojidan/p/4165818.html
Copyright © 2020-2023  润新知