jQuery回調、遞延對象總結(上篇)—— jQuery.Callbacks

前言:

做爲參數傳遞給另外一個函數執行的函數咱們稱爲回調函數,那麼該回調又是不是異步的呢,何謂異步,如:做爲事件處理器,或做爲參數傳遞給html

(setTimeout,setInterval)這樣的異步函數,或做爲ajax發送請求,應用於請求各類狀態的處理,咱們能夠稱爲異步回調,jQuery.Callbacksajax

爲咱們封裝了一個回調對象模塊,咱們先來看一個應用場景:數組

 

// 爲何jQuery中的ready事件能夠執行多個回調,這得益於咱們的jQuery.Deferred遞延對象(是基於jQuery.Callbacks回調模塊)
jQuery(function($) {
    console.log('document is ready!');
    // do something
});

jQuery(function($) {
    // do something
});

// 實現原型
// jQuery.Deferred版代碼
var df = jQuery.Deferred();
df.resolve(); // 在ready事件中調用

// 能夠屢次執行綁定的函數
df.done(fn1, fn2, fn3);
df.done(fn4);
// ...


// jQuery.Callbacks版代碼
var cb = jQuery.Callbacks('once memory');
cb.fire(); // 在ready事件中調用

// 能夠屢次執行綁定的函數
cb.add(fn1, fn2, fn3);
cb.add(fn4);
// ...

 

如今咱們知道jQuery中的ready事件是能夠這樣執行多個回調的,要想深刻理解其源碼,讓咱們繼續看下面吧緩存

 

jQuery回調、遞延對象總結篇索引:app

jQuery回調、遞延對象總結(上篇)—— jQuery.Callbacks異步

jQuery回調、遞延對象總結(中篇) —— 神奇的then方法函數

jQuery回調、遞延對象總結(下篇) —— 解密jQuery.when方法oop

 

 

1、jQuery.Callbacks設計思路

使用一個私有變量list(數組)存儲回調,執行jQuery.Callbacks函數將返回一個能夠操做回調列表list的接口對象,
而傳入jQuery.Callbacks函數的options參數則用來控制返回的回調對象操做回調列表的行爲this

 

回調對象中的方法spa

{
    add:        增長回調到list中
    remove:     從list中移除回調
    fire:       觸發list中的回調
    fired:      回調對象是否執行過fire方法
    fireWith:   觸發list中的回調,第一個參數爲執行域
    has:        判斷函數是否在list中
    empty:      將list致空,list = [];
    lock:       鎖定list
    locked:     是否鎖定
    disable:    禁用回調對象
    disabled:   是否禁用
}

 

參數標誌:

options = {
    once:       回調對象僅觸發(fire)一次

    memory:     跟蹤記錄每一次傳遞給fire函數的參數,在回調對象觸發後(fired),
                將最後一次觸發(fire)時的參數(value)傳遞給在add操做後即將被調用的回調

    unique:     在add操做中,相同的函數僅只一次被添加(push)到回調列表中

    stopOnFalse:當回調函數返回false,中斷列表中的回調循環調用,且memory === false,阻止在add操做中將要觸發的回調
}

 

2、源碼解析

var // Used for splitting on whitespace
    core_rnotwhite = /\S+/g;

var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
// 將字符串格式選項轉化成對象格式形式,並存儲在緩存對象optionsCache[options]中
// 該緩存起做用適用於執行屢次jQuery.Callbacks函數,且傳遞options參數一致,咱們在jQuery.Deferred
// 源碼就能夠看到tuples二維數組中執行了兩次jQuery.Callbacks('once memory')
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ 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 ] || createOptions( options ) ) :
        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
        fire = function( data ) {
            memory = options.memory && data;
            fired = true;
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true;
            // 迭代list回調列表,列表中的回調被應用(或執行回調)
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                // 若是回調返回false,且options.stopOnFlase === true,則中斷循環
                // 注:data[1]是一個僞數組(self.fire方法中的arguments(參數集合))
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {                    
                    memory = false; // To prevent further calls using add   // 阻止在add操做中可能執行的回調
                    break;
                }
            }
            firing = false;
            if ( list ) {
                // (options.once === undefined),回調對象能夠觸發屢次
                if ( stack ) {
                    // 處理正在執行的回調中的fireWith操做
                    // 注:若是執行的回調中真的擁有fire或fireWith操做,那麼列表中的回調將會無限循環的執行,請看實例1
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                }
                // (options.once === true && options.memory === true)
                // 回調列表致空,但容許add繼續添加並執行回調
                else if ( memory ) {
                    list = [];
                }
                // (options.once === true && options.memory === undefined)
                // 禁用回調對象
                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(options.once === true)時,
                    // 咱們應該使用memory(記錄的最後一次fire時的參數)當即調用回調
                    } 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
                            // 在回調對象觸發(fire)時,若是firingLength、firingIndex(正在執行的回調在列表list中的索引index)
                            // 大於等於 移除的回調的索引(index),分別減一,確保回調執行隊列中未執行的回調依次執行
                            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
            // 將列表致空,list = []; firingLenght = 0;
            empty: function() {
                list = [];
                firingLength = 0;
                return this;
            },
            // Have the list do nothing anymore
            // 禁用回調對象
            // 將list賦值爲undefined就可使self中的add,remove,fire,fireWith方法中止工做
            // 我認爲這裏把stack、memory賦值爲undefined與否是沒有任何關係的
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // Is it disabled?
            disabled: function() {
                return !list;
            },
            // Lock the list in its current state
            // 鎖定回調列表
            // 若是(fired !== true || options.memory === false),則視爲禁用(disable)
            // 若是(fired === true && options.memory === true),則視爲options.once === true
            // 請看實例2
            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 ) {
                // 回調對象未執行過fire 或且 能夠執行屢次(options.once === false)
                // 若是(fired === true && options.once === true),則不會執行fire操做
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {
                        stack.push( args );
                    } 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、實例

實例一、 處理回調函數中的fire,或fireWidth操做

var cb = jQuery.Callbacks();

var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); cb.fire();  };
var fn3 = function(arg){ console.log( arg + '3' ); };

cb.add(fn1, fn2, fn3);

cb.fire('fn'); // 其中回調fn1,fn2,fn3無限制的循環調用

/*
控制檯將無限制輸出以下:
fn1
fn2
fn3
fn1
fn2
fn3
fn1
fn2
fn3
.
.
.
*/

 

實例二、 鎖定(lock)操做各類場景中的用法

var cb1 = jQuery.Callbacks();
var cb2 = jQuery.Callbacks('memory');
var cb3 = jQuery.Callbacks('memory');

var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); };
var fn3 = function(arg){ console.log( arg + '3' ); };

// 若是options.memory !== true,鎖定操做視爲禁用回調對象
cb1.add(fn1);
cb1.lock();
// 如下操做無任何反應
cb1.add(fn2);
cb1.fire('fn');

// 若是fried !== true,鎖定操做也視爲禁用回調對象
cb2.add(fn1);
cb2.lock();
// 如下操做無任何反應
cb2.add(fn2);
cb2.fire('fn');

// 若是(fired === true && options.memory === true),鎖定操做相似控制標誌once(options.once === true);
cb3.add(fn1);
cb3.fire('fn'); // fn1,此時fired === true
cb3.lock();     // 像是傳入了'once'標誌,jQuery.Callbacks('once memory');
cb3.add(fn2);   // fn2
cb3.fire('fn'); // 再次觸發,無任何反應
cb3.add(fn3);   // fn3


// 再來看看jQuery.Deferred中的一段源碼
var tuples = [
    // action, add listener, listener list, final state
    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
    [ "notify", "progress", jQuery.Callbacks("memory") ]
];

// Handle state
if ( stateString ) {
    list.add(function() {
        // state = [ resolved | rejected ]
        state = stateString;

    // [ reject_list | resolve_list ].disable; progress_list.lock
    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}

/*
    當執行了tuples中前面兩組中任意一個回調對象的fire方法時,後一組回調對象被鎖定,
    至關於(fired === true && options.memory === true),後一組回調對象實際爲執行
    jQuery.Callbacks('once memory')生成的回調對象。
*/

 

PS: 若有描述錯誤,請幫忙指正,若是大家有不明白的地方也能夠發郵件給我,

  如需轉載,請附上本文地址及出處:博客園華子yjh,謝謝!

相關文章
相關標籤/搜索