jquery源碼分析(四)——回調對象 Callbacks

借用百度百科來講明下回調函數:ajax

     回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一個函數,當這個指針被用來調用其所指向的函數時,咱們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。設計模式

    jQuery回調對象實現剛好利用了設計模式中的觀察者模式思想,觀察者模式 (pub/sub) 的背後,總的想法是在應用程序中加強鬆耦合性。並不是是在其它對象的方法上的單個對象調用。一個對象做爲特定任務或是另外一對象的活動的觀察者,而且在這個任務或活動發生時,通知觀察者。觀察者也被叫做訂閱者(Subscriber),它指向被觀察的對象,既被觀察者(Publisher 或 subject)。當事件發生時,被觀察者(Publisher)就會通知觀察者(subscriber)數組

    具體到開發中,咱們會在事件觸發,定時器,ajax、動畫(transformend)使用回調函數。閉包

    jQuery中回調函數隊列管理模塊:Callbacks從1.6版中的_Deferred對象中抽離出來的。其設計原理是開始構建一個存放回調的數組,再經過add、remove、fire、lock等操做來控制函數隊列的管理,並提供once、memory、unique、stopOnFalse四個option進行一些特殊的控制。app

代碼以下:函數

var rnotwhite = (/\S+/g);//匹配空格,返回數組

// 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.match( rnotwhite ) || [], function( _, flag ) {//接受的字符串的組合傳參數,可使用空格分割
        object[ flag ] = true;
    });
    return object;
}

/*
 * Create a callback list using the following parameters:

 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options://這裏提供四個可選參數once、memory、unique、stopOnFalse 對函數隊列進行特殊控制。
 */
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 && [],//傳遞once參數後,則回調執行完畢以後,list會被清空([])或置爲undefined
        // Fire callbacks
        fire = function( data ) {//閉包方法
            memory = options.memory && data;
            fired = true;//用來判斷回調隊列是否被執行過一次
            firingIndex = firingStart || 0;//上次fire的位置,用做設置memory時,add以後直接觸發回調。
            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; // To prevent further calls using add---//強制修改memory
                    break;
                }
            }
            firing = false;
            if ( list ) {
                if ( stack ) {//是否爲false,初始化時是經過是否傳遞once來控制,
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//這裏清空目的在於,add以後直接fire新添加的回調。
                    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 ) {//添加回調list集合
                        jQuery.each( args, function( _, 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" ) {
                                // Inspect recursively
                                add( arg );
                            }
                        });
                    })( arguments );
                    // Do we need to add the callbacks to the
                    // current firing batch?
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we're not firing then
                    // we should call right away
                    } else if ( memory ) {//這裏若是option.memory有值的話,memory已經被修改成[context,arguments]
                        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
                            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 = [];
                firingLength = 0;
                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 ) {
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {
                        stack.push( args );
                    } else {
                        fire( args );//若$.Callback調用時設置memory,則會閉包中memory被修改成此時的args<==>[context,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;
};

 

一、功能上看:callbacks能夠用來在隊列中添加回調,執行回調,刪除回調等等。並提供一些參數如once,memory,unique等來進行特殊需求的控制。oop

二、回調模塊結構組織:首先構建一個存放回調的隊列,如var list = [],經過閉包使回調隊列所佔內存空間不被釋放添加回調時,將回調push進list,fire時遍歷list將回調隊列中函數執行一遍。動畫

  jQuery.Callbacks()的API列表以下this

callbacks.add()        往回調隊列中添加一個回調或回調的集合。
callbacks.disable()    禁用回調列表中的回調
callbacks.disabled()   判斷回調列表是否已被禁用。 
callbacks.empty()      從列表中刪除全部的回調.
callbacks.fire()       用給定的參數執行全部的回調
callbacks.fired()      判斷是否回調隊列是否被至少fire一次。 
callbacks.fireWith()   訪問給定的上下文和參數列表中的執行全部回調。
callbacks.has()        肯定列表中是否包含一個回調
callbacks.lock()       鎖定當前狀態的回調列表。
callbacks.locked()     肯定回調列表是否已被鎖定。
callbacks.remove()     從回調列表中的刪除一個回調或回調集合。

代碼定義了多個局部變量,來看看他們用處:spa

list:保存回掉函數隊列  經過閉包使回調數組所佔內存空間不被釋放,添加回調時,將回調push進list,執行時則遍歷list執行回調

firing:判斷是否正在執行回調隊列。

firingStart:記住當前fire的位置,當參數爲memory時使用到。

stack:若是當前正在執行回調(或者說回掉隊列未執行完),即firing爲true,將要fire的回調保存到棧中,若未設置了once爲true ,待回調隊列執行完成以後就會執行該回掉。若設置了once 則確保這個回調列表只執行( .fire() )一次。

memory :若設置了memory,保持之前的值,後面添加到這個回調隊列會當即執行該回調(像一個遞延 Deferred)

 firingStart:用於add 回調以後做爲當即執行該回調的索引(在設置memory以後使用)

 

重點提下fire方法: self.fire –> self.fireWith –> fire 最終執行代碼是內部私有的fire方法,內部幾個邏輯處理了如下幾種狀況

(1)、每fire 一次都遍歷list回調隊列,直到結束或者有一個回調函數返回false (只有設置了stopOnFalse控制邏輯才起做用),才停止執行回調

(2)、接下來處理了,回調隊列正在執行時(firing爲true),add 了一個回調,並不會當即處理而是push 到stack以後。 待回調隊列執行完成後再根據stack判斷是否fire新增的回調。

 disable方法:list = stack = memory = undefined; 後續全部操做都失效

 

三、參數的使用方式:參數用 一個用空格標記分隔的標誌可選列表,用來改變回調列表中的行爲:經過如下這些參數進行特殊需求的控制

  • once: 確保這個回調列表只執行( .fire() )一次.
  • memory: 保持之前的值,當執行add以後,將運行添加到這個列表的後面的最新的回調,參數保存在memory中(像一個遞延 Deferred).
  • unique: 確保一次只能添加一個回調(因此在列表中沒有重複的回調).
  • stopOnFalse: 當一個回調返回false 時中斷調用

    當新建一個Callbacks回調隊列時,能夠經過這些參數定義回調隊列fire 以後的行爲。

默認狀況下,回調列表(不傳遞參數狀況下)能夠被屢次觸發(fire),下面來看看參數具體如何使用及其在源碼中如何發揮做用的。

(1)、var callbacks = $.Callbacks( "once" ); —— fire一次以後list被清空

(2)、var callbacks = $.Callbacks( "memory" );—— memory的實現思路就是回調隊列list在fire以後(無論是否設置once),之後add的時候自動調用fire,將新添加的回調執行一遍。(相似遞延)

(3)、var callbacks = $.Callbacks( "unique" );——控制回調函數的惟一性。經過has方法判斷是否存在隊列中。

(4)、var callbacks = $.Callbacks( "stopOnFalse" );——控制是否須要回調函數返回值是否爲false來終止回調隊列的執行。

(5)、var callbacks = $.Callbacks( "once memory" );—— 保持之前的值,將添加到這個列表的後面的最新的值當即執行調用任何回調,once的時候只容許add一次,在觸發fire以後就會理清掉list。

相關文章
相關標籤/搜索