jQuery Callbacks

iteye_3829發表於2013-05-21

jQuery.Callbacks是jQuery的多用途核心元件,專職負責回撥函式列隊管理,其在jQuery的$.ajax() 和 $.Deferred()提供了一些基礎功能。

其主要提供了易於管理的批量回撥函式處理的功能。

說到批處理,在Javascript庫中屢見不鮮:

等等…………

為了理解Callbacks函式的實現,我們先來了解下jQuery.each()。

 

each()

我們可以在jQuery的原始檔core.js找到其完整實現:

/*********************
 *    obj: 佇列列表
 *    callback: 回撥函式
 *    args: 回撥函式的引數
 */
jQuery.each = function( obj, callback, args ) {
    var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike( obj );    //obj是否是類Array物件

    //如果有引數
    if ( args ) {
        //如果obj是類Array物件
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.apply( obj[ i ], args );
                if ( value === false ) {
                    break;
                }
            }
        //否則
        } else {
            for ( i in obj ) {
                value = callback.apply( obj[ i ], args );
                if ( value === false ) {
                    break;
                }
            }
        }
    //如果沒有引數,則是一個更為常用的each函式
    } else {
        if ( isArray ) {
            for ( ; i < length; i++ ) {
                value = callback.call( obj[ i ], i, obj[ i ] );
                if ( value === false ) {
                    break;
                }
            }
        } else {
            for ( i in obj ) {
                value = callback.call( obj[ i ], i, obj[ i ] );
                if ( value === false ) {
                    break;
                }
            }
        }
    }

    return obj;
}

藉助這個函式我們jQuery實現了其他each函式。如:

$( "li" ).each(function( index ) {
  console.log( index + ": "" + $(this).text() );
});

這裡的each實際上只是一個沒有args回撥函式引數的jQuery.each。

 

簡易Callbacks

實際上對簡單的回撥函式進行變形,我們也能弄成類似回撥函式佇列的效果:

function dosomething(__callbacks){
    //do something......
    for(var i = __callbacks.length; i--;){
        __callbacks[i]();
    }
}

但jQuery.CallBacks可以為我們提供更豐富的功能,和更方便的管理方法。

 

jQuery.Callbacks(flags)

flags:一個用空格標記分隔的標誌可選列表,用來改變回撥列表中的行為

  • once: 確保這個回撥列表只執行一次(像一個遞延 Deferred).
  • memory: 保持以前的值和將新增到這個列表的後面的最新的值立即執行呼叫任何回撥 (像一個遞延 Deferred).
  • unique: 確保一次只能新增一個回撥(所以有沒有在列表中的重複).
  • stopOnFalse: 當一個回撥返回false 時中斷呼叫
  • callbacks.add(callbacks)

回撥列表中新增一個回撥或回撥的集合。

callbacks:一個函式,或者一個函式陣列用來新增到回撥列表。

  • callbacks.remove(callbacks)

刪除回撥或回撥回撥列表的集合。

callbacks:一個函式或函式陣列,是從回撥列表中刪除。 

  • callbacks.fire(arguments)

呼叫所有回撥函式,並將arguments傳給他們。

arguments:這個引數或引數列表傳回給回撥列表。

  • callbacks.disable()

禁用回撥列表中的回撥。

例子:

function fn1( value ){
    console.log( value );
    return false;
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}
    
var callbacks = $.Callbacks( "unique memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); //重複新增
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.add( fn2 );
callbacks.fire( "baz" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
fn2 says:foo
bar
fn2 says:bar
baz
fn2 says:baz
foobar
*/

 

optionsCache

我們看到上面的例子中,flags引數是以字串形式,每個引數以空格間隔,如:

$.Callbacks( "unique memory" );

大家會如何將字串轉成引數呢?

在這裡Callbacks會通過正規表示式將字串轉陣列,然後再組裝成引數物件,如上面的例子,則最後引數物件是:

{
    unique: true,
    memory: true
}

再將這個物件快取到optionsCache,以便下次使用。

//引數物件快取
var optionsCache = {};

//將字串表達轉成物件表達,並存在快取中
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}

jQuery.Callbacks = function( options ) {
    
    //通過字串在optionsCache尋找有沒有相應快取,如果沒有則建立一個,有則引用
    //如果是物件則通過jQuery.extend深複製後賦給options。
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    //………………
    
}

其他地方的實現比較像一個單一事件的自定義事件處理物件,通過add新增事件處理函式,用remove刪除事件處理函式,用fire觸發事件。

有興趣請參照下面的完整備註。 

 

完整備註

jQuery.Callbacks = function( options ) {

    //通過字串在optionsCache尋找有沒有相應快取,如果沒有則建立一個,有則引用
    //如果是物件則通過jQuery.extend深複製後賦給options。
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var // 最後一次觸發回撥時傳的引數
        memory,
        // 列表中的函式是否已經回撥至少一次
        fired,
        // 列表中的函式是否正在回撥中
        firing,
        // 回撥的起點
        firingStart,
        // 回撥時的迴圈結尾
        firingLength,
        // 當前正在回撥的函式索引
        firingIndex,
        // 回撥函式列表
        list = [],
        // 可重複的回撥函式堆疊,用於控制觸發回撥時的引數列表
        stack = !options.once && [],
        // 觸發回撥函式列表
        fire = function( data ) {
            //如果引數memory為true,則記錄data
            memory = options.memory && data;
            //標記觸發回撥
            fired = true;
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            //標記正在觸發回撥
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // 阻止未來可能由於add所產生的回撥
                    break;    //由於引數stopOnFalse為true,所以當有回撥函式返回值為false時退出迴圈
                }
            }
            //標記回撥結束
            firing = false;
            //如果列表存在
            if ( list ) {
                //如果堆疊存在
                if ( stack ) {
                    //如果堆疊不為空
                    if ( stack.length ) {
                        //從堆疊頭部取出,遞迴fire。
                        fire( stack.shift() );
                    }
                //否則,如果有記憶
                } else if ( memory ) {
                    //列表清空
                    list = [];
                //再否則阻止回撥列表中的回撥
                } else {
                    self.disable();
                }
            }
        },
        // 暴露在外的Callbacks物件
        self = {
            // 回撥列表中新增一個回撥或回撥的集合。
            add: function() {
                if ( list ) {
                    // 首先我們儲存當前列表長度
                    var start = list.length;
                    (function add( args ) {
                        //前面我們看到的jQuery.each,對args傳進來的列表的每一個物件執行操作
                        jQuery.each( args, function( _, arg ) {
                            //得到arg的型別
                            var type = jQuery.type( arg );
                            //如果是函式
                            if ( type === "function" ) {
                                //確保是否可以重複
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            //如果是類陣列或物件
                            } else if ( arg && arg.length && type !== "string" ) {
                                //遞迴
                                add( arg );
                            }
                        });
                    })( arguments );
                    // 如果正在回撥就將回撥時的迴圈結尾變成現有長度
                    if ( firing ) {
                        firingLength = list.length;
                    // 如果有memory,我們立刻呼叫。
                    } else if ( memory ) {
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            // 從列表刪除回撥函式
            remove: function() {
                if ( list ) {
                    //繼續用jQuery.each,對arguments中的所有引數處理
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        //找到arg在列表中的位置
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            //根據得到的位置刪除列表中的回撥函式
                            list.splice( index, 1 );
                            //如果正在回撥過程中,則調整迴圈的索引和長度
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            // 回撥函式是否在列表中
            has: function( fn ) {
                return jQuery.inArray( fn, list ) > -1;
            },
            // 從列表中刪除所有回撥函式
            empty: function() {
                list = [];
                return this;
            },
            // 禁用回撥列表中的回撥。
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // 列表中否被禁用
            disabled: function() {
                return !list;
            },
            // 鎖定列表
            lock: function() {
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            // 列表是否被鎖
            locked: function() {
                return !stack;
            },
            // 以給定的上下文和引數呼叫所有回撥函式
            fireWith: function( context, args ) {
                args = args || [];
                args = [ context, args.slice ? args.slice() : args ];
                if ( list && ( !fired || stack ) ) {
                    //如果正在回撥
                    if ( firing ) {
                        //將引數推入堆疊,等待當前回撥結束再呼叫
                        stack.push( args );
                    //否則直接呼叫
                    } else {
                        fire( args );
                    }
                }
                return this;
            },
            // 以給定的引數呼叫所有回撥函式
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // 回撥函式列表是否至少被呼叫一次
            fired: function() {
                return !!fired;
            }
        };

    return self;
};

相關文章