jQuery.Callbacks 源碼解讀二

1、參數標記

/*
 * once: 確保回調列表僅只fire一次
 * unique: 在執行add操做中,確保回調列表中不存在重複的回調
 * stopOnFalse: 當執行回調返回值爲false,則終止回調隊列的執行
 * momery: 記錄上一次fire時的參數,並在add中傳遞給fire和執行fire,執行時firingIndex爲上一次fire時的firingLength
 */

2、源碼解讀分析

var optionsCache = {},
    // Used for splitting on whitespace
    core_rnotwhite = /\S+/g;

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
    // 多個變量指向同一對象(或數組)引用時,其中一個變量修改了被引用對象的內部結構,其餘引用變量也會表現出來
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true; // optionsCache[ options ][ flag ] = true;
    });
    return object;
}

jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    options = typeof options === "string" ?
        // 緩存全部的參數標誌,當再次傳遞已傳遞過的參數標誌,則使用緩存值optionsCache[ options ]        
        ( optionsCache[ options ] || createOptions( options ) ) :

        // 說明也能夠這樣$.Callbacks({once:true, memory:true})使用
        jQuery.extend( {}, options );

    var // Flag to know if list is currently firing
        firing,
        // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list was already fired
        fired,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = !options.once && [],

        // Fire callbacks
        // data爲fireWith內部整理的args數組
        fire = function( data ) {
            memory = options.memory && data;
            fired = true;

            // 處理在add中,options.memory = true;的狀況
            firingIndex = firingStart || 0;
            firingStart = 0;

            firingLength = list.length;
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                // 正在執行的回調返回值爲false 且 options.stopOnFalse爲true,則終止回調隊列的執行
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // To prevent further calls using add
                    break;
                }
            }
            firing = false;
            if ( list ) {
                // 處理正在執行的回調中執行fireWith的操做;
                if ( stack ) {
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                }
                // 上一分支狀態爲回調執行過,且能夠執行屢次
                // 此時 options.once = true; 這裏將list設置爲[],只是確保下次執行fire時,無回調執行
                // 可是若是 options.memory = true; 仍然會執行add中的fire操做,由於此時回調列表中已有回調
                else if ( memory ) {
                    list = [];
                }
                else {
                    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;
                    (function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            if ( type === "function" ) {
                                // 回調不惟一 或 惟一且不存在,則push
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            }
                            // 遞歸檢查
                            else if ( arg && arg.length && type !== "string" ) {
                                // Inspect recursively
                                add( arg );
                            }
                        });
                    })( arguments );
                    // Do we need to add the callbacks to the
                    // current firing batch?
                    // 正在執行的回調執行了add操做,則更新firingLength
                    if ( firing ) {
                        firingLength = list.length;

                    // With memory, if we're not firing then
                    // we should call right away
                    // 若是options.memory爲true,則再次執行fire,且參數相同,fire中的firingIndex爲此時的firingStart
                    }
                    else if ( memory ) {
                        firingStart = 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
                            // 正在執行的回調執行了remvoe操做,則更新firingLength和firingIndex的值
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                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.
            has: function( fn ) {
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            // Remove all callbacks from the list
            empty: function() {
                list = [];
                return this;
            },
            // Have the list do nothing anymore
            // 禁用add,remove,fire主要方法的工做
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // Is it disabled?
            disabled: function() {
                return !list;
            },
            // Lock the list in its current state
            lock: function() {
                // 若是回調執行過,則將阻止self.fire操做
                // 但若是 options.memory = true,則仍然會執行fire操做
                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 ];
                
                // 回調未執行 或 已執行且能夠執行屢次
                if ( list && ( !fired || stack ) ) {

                    // 正在執行的回調函數執行了fireWith操做( 暗指回調列表已執行過,且能夠執行屢次,stack = []; )
                    // 該函數須要條件執行,或有移除該函數的操做,不然陷入死循環,詳見例2
                    if ( firing ) {
                        stack.push( args );
                    }
                    // 正在執行的回調函數沒有執行fireWith操做
                    else {
                        fire( args );
                    }
                }
                return this;
            },
            // Call all the callbacks with the given arguments
            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;
};

3、示例

例1:數組

$(function(){
    // 定義三個將要增長到回調列表的回調函數fn1,fn2,fn3        
    function fn1(arg){
        console.log( 'fn1 says:' + arg );
        // 在fn1中執行Callbacks.add操做,此時Callbacks函數內部的firingLength將會獲得更新
        $callbacks.add(fn2);
    }
    function fn2(arg){
        console.log( 'fn2 says:' + arg );
    }
    function fn3(arg){
        console.log( 'fn3 says:' + arg );
    }
    
    // Callbacks傳遞了memory
    // 也能夠這樣使用$.Callbacks({ memory: true });
    var $callbacks = $.Callbacks('memory');
    
    // 將fn1增長到回調列表中,由於在fn1中有執行了add(fn2)操做,所以回調列表中的回調爲fn1,fn2
    $callbacks.add(fn1);
    
    // output: fn1 says:foo
    // output: fn2 says:foo
    $callbacks.fire('foo');
    
    // 將以前fire的參數傳遞給最近增長的回調fn3,並執行fn3
    // output: fn3 says:foo
    $callbacks.add(fn3);
    
    // 再執行一次fire,注意此時回調列表中的回調一次是fn1,fn2,fn3,fn2
    // output: fn1 says:baz
    // output: fn2 says:baz
    // output: fn3 says:baz
    // output: fn2 says:baz
    // 若是指望回調列表中只有fn1,fn2,fn3,只需在Callbacks函數中傳入unique
    $callbacks.fire('baz');
});
View Code

例2緩存

$(function(){    
    function fn1(arg){
        console.log( 'fn1 says:' + arg );
    }
    function fn2(arg){
        console.log( 'fn2 says:' + arg );
        $callbacks.fireWith(window, ['yjh']);
        
        // 必定要執行這一步,不然將會陷入死循環
        $callbacks.remove(fn2);
    }
    
    var $callbacks = $.Callbacks();
    $callbacks.add(fn1);
    
    // output: fn1 says:foo
    $callbacks.fire('foo');    

    $callbacks.add(fn2);    
    // output: fn1 says:baz
    // output: fn2 says:baz
    // output: fn1 says:yjh
    $callbacks.fire('baz');
});
View Code

PS:app

此前寫過一篇關於jQuery.Callbacks源碼分析的隨筆,理解不透徹,今天又從新翻閱了一下,記錄一下本身的源碼閱讀,相比以前,感受好多了。ide

閱讀前,能夠先看API,弄清楚四個參數標誌,'once', 'memory', 'unique', 'stopOnFalse', 簡單的執行add, fire操做,而後再看源碼;函數

閱讀順序:oop

一、先閱讀var聲明的變量,fire函數的前半部分,self對象中的add, remove函數,有些難以理解暫時往下看;源碼分析

二、而後閱讀self對象中的fire,fireWith,最後再來閱讀fire函數,弄清楚後再看其餘self對象中的方法。this

轉載請註明出處:博客園華子yjhspa

相關文章
相關標籤/搜索