Jquery Source Reading--Callbacks

1. Simplified allbacks

    //Simplified allbacks
    Jquery.Callbacks = function( options ){
        //Processing incoming parameters
        options =  ...
        //Declare some variables
            //Is the function in the list being callback
        var firing, 
            //Parameters passed when the callback was last triggered
            memory, 
            //Has the function in the list been callback (at least once)
            fired, 
            // Marker to stop callback
            locked, 
            //callback list
            list = [],
            // Repeatable List Callback Function List Execution Data
            queue = [], 
            //Index being callback
            firingIndex = -1, 
            //Start executing callback function, this function is not exposed, avoid external modifications to it
            fire:...
            // Some initialization operations
            // some code

        self = {
            add:            //Add a collection of callbacks or callbacks to the callback list.
            remove:         //Remove a callback or collection of callbacks from the callback list
            has:            //Determines if a callback is provided in the list
            empty:          //Remove all callbacks from the list.
            disable:        //Disable callbacks in callback list
            disabled:       //Determines whether the callback list has been disabled.
            lock:           //Locks the callback list for the current state.
            locked:         //Determines if the callback list is locked.
            fireWith:       //Access all callbacks in the given context and parameter list.
            fire:           //Call all callbacks with the given parameters
            fired:          //Access all callbacks in the given context and parameter list
        }
        return self;
    }

2. Options

Callbacks can pass in four parameters, which can be combined arbitrarily

  • Once ensures that the callback list can only be triggered once (like Deferred)

  • Memory tracks previous values and is immediately touched by the latest "memory" values (like Deferred) in the list

  • unique ensures that callbacks can only be added once (so there are no duplicates in the list)

  • stopOnFalse callback interrupts the call when it returns false.

   function fn1( value ) {
      console.log( "fn1: " + value );
    }

    function fn2( value ) {
      console.log( "fn2: " + value );
      return false;
    }

No parameters

    var callbacks = $.Callbacks();
    callbacks.add(fn1);
    callbacks.fire("2");
    callbacks.add(fn2);
    callbacks.fire("2");

    //output
    // fn1: 2
    // fn2: 2

Once

    var callbacks = $.Callbacks("once");
    callbacks.add(fn1);
    callbacks.fire("2");

    console.log(callbacks.locked());

    callbacks.add(fn2);
    callbacks.fire("2");

    //output 
    // 2
    // true

One only triggers fire() Once, and then fire() is not valid. This callback queue can no longer be callback used (unless memeory is used), and its internal control code is roughly as follows

    self.fireWith:function(){
        if(!locked){
            ...
            fire();
            ...
        }
    }

memory

    var callbacks = $.Callbacks("memory");
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.fire("foo2");

    // output
    // fn1:foo
    // fn2:foo
    //fn1: foo2
    //fn2: foo2 

memory records the parameters used in the last call and executes the callback as soon as a new callback is added, with the following control code:

    self.add:function(){
        ...
        if ( memory && !firing ) {
            fire();
        }
        ...
    }

unique

    var callbacks = $.Callbacks("unique");
    callbacks.add( fn1 );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );

    //output
    //fn1:foo

unique actually prevents duplicate callback functions from being added, and the control code is as follows:

        if ( !options.unique || !self.has( arg ) ) {
            list.push( arg );
        }

stopOnFalse

     var callbacks = $.Callbacks("stopOnFalse");
    callbacks.add( fn1 );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );

    // output
    // fn1:foo
    // fn2:foo

stopOnFalse interrupts the callback earlier with the following control code:

    if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
        options.stopOnFalse ) {
        firingIndex = list.length; // Jump straight to the end of the queue
        memory = false;
    }

Some applications

Late Supplement (to be continued)

Callbacks source (with comments)

/*
 * Create a callback list using the following parameters:
 *
 *  options: an optional list of space-separated options that will change how
 *          the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *  once:           will ensure the callback list can only be fired once (like a Deferred)
 *                  //Ensure that the callback list can only be triggered once (like a delay)
 *
 *  memory:         will keep track of previous values and will call any callback added
 *                  after the list has been fired right away with the latest "memorized"
 *                  values (like a Deferred)
 *                  //Track previous values and invoke any callbacks immediately after the list is triggered by the latest "memory" values, such as Deferred.
 *
 *  unique:         will ensure a callback can only be added once (no duplicate in the list)
 *                  //Ensure that callbacks can only be added once (so they are not repeated in the list)
 *
 *  stopOnFalse:    interrupt callings when a callback returns false
 *                  //The call was interrupted when the callback returned false.
 *
 */
jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first) 

    //Resolve incoming parameter problem string || Object
    //If it is a string, look in the cache before converting the string to an object through createOptions()
    //If it is an object, it is copied deep through jQuery.extension and assigned to options.
    options = typeof options === "string" ?
        createOptions( options ) :
        jQuery.extend( {}, options );

    var // Flag to know if list is currently firing
        firing, //Is the function in the list being callback

        // Last fire value for non-forgettable lists
        memory, //Parameters passed when the callback was last triggered

        // Flag to know if list was already fired
        fired, //Has the function in the list been callback (at least once)

        // Flag to prevent firing
        locked, // Marker to stop callback

        // Actual callback list
        list = [], //callback list

        // Queue of execution data for repeatable lists
        queue = [], // Repeatable List Callback Function List Execution Data

        // Index of currently firing callback (modified by add/remove as needed)
        firingIndex = -1, //Index being callback

        // Execute callback function, this interface is not exposed, it is safe
        // Fire callbacks
        fire = function() {

            //once control code
            // Enforce single-firing
            locked = locked || options.once;

            // Execute callbacks for all pending executions,
            // respecting firingIndex overrides and runtime changes
            // Start executing callback Tags
            fired = firing = true;

            // firingIndex is reset to -1 after each fire() has ended
            //  This for loop actually executes only once 
            for ( ; queue.length; firingIndex = -1 ) {
                memory = queue.shift();
                while ( ++firingIndex < list.length ) {

                    // Run callback and check for early termination
                    // Early interruption of callback, stopOnFalse's control code, also executes callback in callback function list
                    if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
                        options.stopOnFalse ) {

                        // Jump to end and forget the data so .add doesn't re-fire
                        //  Move directly to the end of the queue
                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            // Forget the data if we're done with it
            // Clear Last Data
            if ( !options.memory ) {
                memory = false;
            }

            //Callback end tag
            firing = false;

            // Clean up if we're done firing for good
            // After each fire(), you need to check some control variables for new adjustments
            if ( locked ) {

                // Keep an empty list if we have data for future add calls
                // If there is memory, the callback function list is emptied after each fire()
                if ( memory ) {
                    list = [];

                // Otherwise, this object is spent
                } else {
                    list = "";
                }
            }
        },

        // Actual exposed interfaces
        // Actual Callbacks object
        self = {

            // Add a callback or a collection of callbacks to the list
            add: function() {
                if ( list ) {

                    // If we have memory from a past run, we should fire after adding
                    if ( memory && !firing ) {
                        // Adjust the callback order to pave the way for this callback directly
                        firingIndex = list.length - 1;
                        // Recent arguments are rejoined, and after add, the function is called immediately
                        queue.push( memory );
                    }

                    ( function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            if ( isFunction( arg ) ) {
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            //If it is an array of classes or objects, recursively
                            } else if ( arg && arg.length && toType( arg ) !== "string" ) {

                                // Inspect recursively
                                add( arg );
                            }
                        } );
                    } )( arguments );

                    if ( memory && !firing ) {
                    // Callback directly, where firingIndex = list.length - 1 
                        fire();
                    }
                }
                // call chaining
                return this;
            },

            // Remove a callback from the list
            remove: function() {
                jQuery.each( arguments, function( _, arg ) {
                    var index;
                    // There may be multiple callbacks to delete in arguments
                    // The significance of the while loop is to remove the same function references from the list of functions with the help of a powerful jQuery.inArray (in the case where unique s are not set)
                    // jQuery.inArray continues searching each time it returns the index of the found element as its third argument until the end of the list of functions
                    // splice(index,1) deletion
                    while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {

                        list.splice( index, 1 );

                        // Handle firing indexes
                        //Because an array has been deleted and the subscript is less than or equal to the function subscript being executed, firingIndex--
                        if ( index <= firingIndex ) {
                            firingIndex--;
                        }
                    }
                } );
                return this;
            },

            // Check if a given callback is in the list.
            // If no argument is given, return whether or not list has callbacks attached.
            // Find out if there is a corresponding callback function in the callback function list
            has: function( fn ) {
                return fn ?
                    jQuery.inArray( fn, list ) > -1 :
                    list.length > 0;
            },

            // Remove all callbacks from the list
            // Clear all callbacks in the callback function list by pointing directly to [].
            empty: function() {
                if ( list ) {
                    list = [];
                }
                return this;
            },

            // Disable .fire and .add
            // Abort any current/pending executions
            // Clear all callbacks and values
            // Disable callbacks in the callback function list. 
            disable: function() {
                locked = queue = [];
                list = memory = "";
                return this;
            },
            // Is Disabled
            disabled: function() {
                return !list;
            },

            // Disable .fire
            // Also disable .add unless we have memory (since it would have no effect)
            // Abort any pending executions
            // Prohibited.fire 
            // Prohibit.add unless memory is used
            // Lock List
            lock: function() {

                locked = queue = [];
                // Without memory disable.Add
                if ( !memory && !firing ) { 
                    list = memory = "";
                }
                return this;
            },
            // Is the list locked
            locked: function() {
                //  !! Force type conversion, equivalent to Boolean(locked)
                return !!locked;
            },

            //Call all callback functions with given context and parameters
            // Call all callbacks with the given context and arguments
            fireWith: function( context, args ) {
                if ( !locked ) {

                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];

                    //Parameters to be called by all callback functions
                    queue.push( args );

                    if ( !firing ) {
                        fire();
                    }
                }
                return this;
            },
            //  Call all callback functions with the given parameters
            // Call all the callbacks with the given arguments
            fire: function() {

                self.fireWith( this, arguments );
                return this;
            },

            // Whether the callback function list is called at least once
            // To know if the callbacks have already been called at least once
            fired: function() {
                //  !! Force type conversion, equivalent to Boolean(fired)
                return !!fired;
            }
        };

    return self;
};

Tags: JQuery less

Posted on Mon, 06 Apr 2020 09:20:14 -0700 by gilsontech