深入jQuery中的Callbacks()


引入

  初看Callbacks函數很不起眼,但仔細一瞅,發現Callbacks函數是構建jQuery大廈的無比重要的一個基石。jQuery中幾乎所有有關異步的操作都會用到Callbacks函數。

為什么搞了個Callbacks函數?

  1  在 js 開發中,經常會遇到同步和異步這兩個概念。

  2  在javascript中神馬是同步?神馬是異步? 聽我講一個相親的故事(本故事並不准確,僅供參考):

    1 藤籃是一個漂亮姑娘,明年就要30歲了可現在還沒有對象,於是,她的母親給她報名了兩家相親機構,一家名叫同步相親機構,另一家叫異步相親機構。

    2 同步相親機構:這個機構的負責人很呆板,嚴格遵從“先來后到”的理念

      負責人首先給藤籃一個小冊子,里面記錄了很多的男士資料,讓藤籃從里面找一個心儀的男士,然后安排藤籃與他見面。

      藤蘭很快選中了令狐沖。負責人告訴藤籃:“令狐沖明天和任女士有約,你們只能后天見面了“。藤籃說:“好的,我正好准備准備”。

      結果兩天過后,負責人告訴藤籃:因為昨天任女士有事,沒能和令狐沖見面,所以我們安排今天任女士與令狐沖見面,你到明天再約吧!

      藤籃很生氣:既然昨天任女士有事,為啥不安排讓我昨天和令狐沖見面呢?

      負責人說:不行!俺們講究的是先來后到! 因為任女士先約的令狐沖,甭管怎么着,你都得排在任女士后面

      藤籃很生氣! 於是來到了異步相親機構。

    3 異步相親機構:這個機構的負責人則很靈活:

      一般情況下遵從先來后到的理念,特殊情況特殊對待。

      藤籃很喜歡這個負責人的理念,於是和這里的負責人攜手一生.......

    4 再來總結一下:Javascript語言的執行環境是"單線程",所謂"單線程",就是指一次只能完成一件任務。

      同步就是: 后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的

      異步就是:一個任務可能有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而執行回調函數

        后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的

  3  上面異步中講到,一個任務可能有一個或多個回調函數,假如a任務有a1,a2,a3這三個回調函數,b也需要a1,a2,a3這三個回調函數。我們需要這樣做:

    function a1(){
        
    }
    function a2(){
        
    }
    function a3(){
        
    }
    var a = setTimeout(function(){
        a1();
        a2();
        a3();
    },1000);
    var b = setTimeout(function(){
        a1();
        a2();
        a3();
    },2000)
View Code

  4  上面的代碼很麻煩是不是?Callback就是來解決這種麻煩!!

Callbacks如何解決這種麻煩? 

先來說一下大體思路:

  1  首先我們每次調用Callbacks(),都會返回一個callbacks對象,這個對象有一個僅僅只有自己才能訪問的數組(就是用了閉包唄) 

  2  這個數組就是用來存儲 回調函數的。

  3  callbacks對象有許多方法,可以對數組進行操作,比如說:添加(add),清空(empty),刪除(remove),運行數組中的所有回調函數(fire)....... 

  4  那么,我們上面---為什么搞了個Callbacks函數?---的第三條  任務a,任務b可以如下改寫:

    function a1(){

    }
    function a2(){

    }
    function a3(){

    }
    var allCallback = $.Callbacks();
    allCallback.add(a1,a2,a3); 
    
    var a = setTimeout(function(){
        allCallback.fire()
    },1000);
    var b = setTimeout(function(){
        allCallback.fire()
    },2000)
View Code

 

升華:

  Callbacks可不僅僅只實現了這些,他還提供了很多參數: var a = Callbacks('once memory unique stopOnFalse ')

    once: 如果創建Callbacks時加入該參數,則運行數組中的所有回調函數之后,也就是fire()之后,會清空數組。

    memory: 會保存上一次運行fire(args)時的參數args,每當添加一個新的回調函數到數組中,會立即使用args作為參數調用新加的函數一次

    unique: 確保數組中的回調函數互不相同

    stopOnFalse: 當運行fire()時,若某個回調函數返回false,則立即終止余下的回調函數執行

  盡管這些參數可能有些初看沒啥用。 但是不得要說,jQuery的開發人員真的很細致!

源碼講解:  

    define([
    "./core",
    "./var/rnotwhite"
], function( jQuery, rnotwhite ) {

// String to Object options format cache
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
/*
如果: var a = $.Callback('once memory')
則 optionsCache中會有這么一項:"once memory":{memory:true,once:true}
*/
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:
 *
 *    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 = 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  list是否已經被fire函數調用過
        fired,
        // Flag to know if list is currently firing  當前是否正在調用fire函數
        firing,
        // First callback to fire (used internally by add and fireWith)  第一個被執行的回調函數在list的位置
        firingStart,
        // End of the loop when firing   fire函數要運行的回調函數的個數
        firingLength,
        // Index of currently firing callback (modified by remove if needed)  當前正在執行的回調函數的索引
        firingIndex,
        //回調函數數組
        list = [],
        // Stack of fire calls for repeatable lists  可重復的回調函數棧。我們可能會短時間內執行多次fire(),若當前的fire()正在迭代執行回調函數,而緊接着又執行了一次fire()時,會將下一次的fire()參數等保存至stack中,等待當前的fire()執行完成后,將stack中的fire()進行執行
        stack = !options.once && [],
        // Fire callbacks
        fire = function( data ) {
            // data[0] 是一個對象,data[1]則是回調函數的參數
            memory = options.memory && data;  // 很精妙,仔細體會一下這句代碼,如果調用Calbacks時傳入了memory,則memory = data,否則memory = false
            fired = true; // 在調用本函數時,將fired狀態進行修改
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true; // 迭代回調函數之前,將firing狀態進行修改
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&
                    options.stopOnFalse ) { // 運行回調函數的同時,檢測回調函數是否返回false,若返回false,且調用Callbacks時傳入stopOnFalse參數,則終止迭代

                    memory = false; // To prevent further calls using add 既然終止迭代了,那么之后添加的回調函數都不應該被調用,將memory設置為false
                    break;
                }
            }
            firing = false;  // 迭代回調函數完成后,將firing狀態進行修改
            if ( list ) {
                if ( stack ) {  // 沒有使用once參數
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) { // 使用了once memory參數,則在迭代完回調函數之后清空list
                    list = [];
                } else { // 其他
                    self.disable();
                }
            }
        },
        // Actual Callbacks object
        self = {
            // 將一個新的回調函數添加至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" ) {
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg ); // 若參數中的元素為函數且(無unique參數或者list中沒有該函數),則將該函數添加至list末尾
                                }
                            } else if ( arg && arg.length && type !== "string" ) { //  arg的長度不為0且每項的類型不為字符串,也就是args為這種情況:[[fun1,fun2...],[fun3,fun4]](不僅限於這種情況)
                                // Inspect recursively
                                add( arg );
                            }
                        });
                    })( arguments );
                    // Do we need to add the callbacks to the
                    // current firing batch?
                    // 當Callback中的firingLength變為 動態的! 也就是:只要我們向list中添加了一個新的回調函數,即使在fire()運行過程中,改變也能立即體現出來
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we're not firing then
                    // we should call right away
                    } else if ( memory ) { // 如果當前沒有執行回調函數,且存在memory參數,則執行新添加的回調函數
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            // Remove a callback from the list 將一個回調函數從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
            // 使用傳入的context作為當前函數的執行上下文
            fireWith: function( context, args ) {
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {
                        stack.push( args ); // 如果當前正在迭代執行回調函數,則將新的fire參數推入stack中
                    } 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
            // 用來確定當前callback對象是否被fire()過
            fired: function() {
                return !!fired;
            }
        };

    return self;
};

return jQuery;
});

 

 

 


免責聲明!

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



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