學習jQuery.Callbacks


Callbacks是JQ的一個回調對象,可以用來添加回調,執行回調,刪除回調等等。並提供一些參數如once,memory,unique等來進行特殊需求的控制。這里就不舉例說明Callbacks的用法了。具體詳細說明可以參見:http://api.jquery.com/jQuery.Callbacks/

我們學習源碼,需先了解如何使用,這里假設我們已經知道如何使用Callbacks了。

他的實現思路就是: 構建一個存放回調的數組,如var list = [],通過閉包使這條回調數組保持存在。添加回調時,將回調push進list,執行則遍歷list執行回調。

看思路貌似很簡單,我們就直接來看源碼吧,對於源碼的閱讀,我的習慣是大概過一次源碼,看看注釋了解下大概是作何功用,再直接運行最簡單的示例,跟蹤下它的執行流程,這個過程中各變量的變化,返回值等等。

這里,在過源碼的時候,我們就知道當調用了var cal = $.Callbacks()的時候,返回來的是一個對象(self),這個對象里面實現了add,remove,fire等方法,也就是說,可以用cal.add(fn)來添加回調,cal.remove()來刪除回調。

好吧,直接上代碼解釋,只解釋了add和fire兩個重要方法,其他都較簡單。

// String to Object options format cache
var optionsCache = {};
  
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.split( core_rspace ), function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}
  
/*
 * 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)
 *
 *  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)
 *
 *  unique:         will ensure a callback can only be added once (no duplicate in the list)
 *
 *  stopOnFalse:    interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {
  
    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    // options 這里設計為一個對象,這一步就是先判斷下options緩存里有沒這個對象存在,有直接拿來用,沒有則通過createOptions方法將傳過來的字符串轉化為一個對象,對象形式如下:
    // options = { 'once': true,'memory': true}
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );
  
    var // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list was already fired
        fired,
        // Flag to know if list is currently firing
        firing,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = !options.once && [],
        // Fire callbacks
        fire = function( data ) {
            memory = options.memory && data;  //當$.Callbacks('memory')時,保存data,data為數組[context,args]
            fired = true;   //表示已經執行,用於表示隊列里的回調已經執行過一次
            firingIndex = firingStart || 0;   //執行隊列的下標,相當於普通循環的i,當$.Callbacks('memory')時,需設置firingIndex
            firingStart = 0;                  //重置隊列起始值
            firingLength = list.length;       //保存隊列的長度
            firing = true;                    //標示正在執行中
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {  //
                //遍歷回調數組,執行每一個回調,當回調返回false且有傳入stopOnFalse時,也就是$.Callbacks('stopOnFalse'),中止后面回調的執行. PS:這個循環每次都判斷list,這個沒有必要,可以提到外面判斷一次即可
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // To prevent further calls using add //當$.Callbacks('memory stopOnFalse')時,memory的作用將失效
                    break;
                }
            }
            firing = false;  //函數執行完后,將執行中的標示設為false
            if ( list ) {
                if ( stack ) {  //執行完回調后,看一下stack是否有回調,有拿出來執行
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {  //如果沒有stack,證明傳了once,這里的Callbacks會是這樣:$.Callbacks('once memory')
                    list = [];
                } else {                //當是$.Callbacks('once')的時候
                    self.disable();
                }
            }
        },
        // Actual Callbacks object
        self = {
            // Add a callback or a collection of callbacks to the list
            add: function() {
                if ( list ) {
                    // First, we save the current length
                    var start = list.length;  //添加回調函數之前,先保存當前回調函數列表的長度,主要用於當Callbacks傳入memory參數時
  
                    //這里用了一個立即執行的add函數來添加回調
                    //直接遍歷傳過來的arguments進行push
                    (function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            //如果所傳參數為函數,則push
                            if ( type === "function" ) {
                                if ( !options.unique || !self.has( arg ) ) {  //當$.Callbacks('unique')時,保證列表里面不會出現重復的回調
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && type !== "string" ) {  //假如傳過來的參數為數組或array-like,則繼續調用添加,從這里可以看出add的傳參可以有add(fn),add([fn1,fn2]),add(fn1,fn2)
                                // Inspect recursively
                                add( arg );
                            }
                        });
                    })( arguments );
  
                    // Do we need to add the callbacks to the
                    // current firing batch?
                    // 這里是我不解的地方,firing是標識回調數組正在執行中,也就是fire正在執行,那這里就重置回調數組的長度,但我不知道什么樣的代碼下這里會執行到
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we're not firing then
                    // we should call right away
                    // 在fire方法里,只有options.memory為真時,memory才有值,所以,這里的memory保存的是上一次fire時的memory值,而這個的作用就是要立即執行新添加的回調,讓新添加的回調也能輸出之前fire時傳的值。
                    //這里也是$.Callbacks('memory')這個參數作用的地方,有了這個參數,每次add也會執行一次memory
                    } else if ( memory ) {   
                        firingStart = start;   //上面保存的start值產生作用的地方
                        fire( memory );
                    }
                }
                return this;
            },
            // Remove a callback from the list
            remove: function() {
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            // Handle firing indexes
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            // Control if a given callback is in the list
            has: function( fn ) {
                return jQuery.inArray( fn, list ) > -1;
            },
            // Remove all callbacks from the list
            empty: function() {
                list = [];
                return this;
            },
            // Have the list do nothing anymore
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // Is it disabled?
            disabled: function() {
                return !list;
            },
            // Lock the list in its current state
            lock: function() {
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            // Is it locked?
            locked: function() {
                return !stack;
            },
            // Call all callbacks with the given context and arguments
            fireWith: function( context, args ) {
                args = args || [];
                args = [ context, args.slice ? args.slice() : args ];  //slice只用於字符串或數組,這里為什么要判斷一下是不是字符串或數組呢,不明
                if ( list && ( !fired || stack ) ) {  //fired表示已經執行過,如果已經執行過了,就要看stack了,他為真才會繼續執行
                    if ( firing ) {  //firing表示執行中,如果是在執行中,則將其推入stack,stack在這里相當於一個緩存數組,用於當fire忙時暫存下回調,但我也是整不出一段代碼讓這里執行到,暫時不明白
                        stack.push( args );
                    } else {
                        fire( args );  //執行回調
                    }
                }
                return this;
            },
            // Call all the callbacks with the given arguments
            // 直接調用fire,回調執行的上下文是self,而上面的fireWidth則可以通過傳入context改變回調的執行上下文
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // To know if the callbacks have already been called at least once
            fired: function() {
                return !!fired;
            }
        };
  
    return self;
};

此文所講jQuery版本為1.8.3 : http://code.jquery.com/jquery.js


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM