jQuery 源碼解析(八) 異步隊列模塊 Callbacks 回調函數詳解

異步隊列用於實現異步任務和回調函數的解耦,爲ajax模塊、隊列模塊、ready事件提供基礎功能,包含三個部分:Query.Callbacks(flags)、jQuery.Deferred(funct)和jQuery.when()。本節講解Callbacks,也就是回調函數列表html

回調函數用於管理一組回調函數,支持添加、移除、觸發、鎖定和禁用回調函數,爲jQuery.ajax、jQuery.Deferred()和ready()事件提供基礎功能,咱們也能夠基於它編寫新的組件。jquery

使用方法:$.Callbacks(flags),flags是一個參數,用於指定傳遞的標記,能夠爲空,能夠設置爲如下四個選項之一,或者任意組合也能夠,以下:ajax

  unique             確保一個回調函數只能被添加一次
  stopOnFlase   當某個回調函數返回false時中斷執行
  once                確保回調函數列表只能被觸發一次 
  memory           記錄上一次觸發回調函數列表時的參數,以後添加的任何函數都將用記錄的參數值當即調用數組

執行成功後返回一個對象,該函數含有以下幾個方法:緩存

  add(fn/arr)           添加一個/多個回調函數到list數組中app

  remove(fn1,fn2,fn3...)      從list中移除多個回調函數less

  empty()          清空list數組異步

  disable()          禁用列表,使他再也不作任何事情,該操做不可還原函數

  disabled()          判斷是否已禁用列表,若是已經禁用了則返回true工具

  lock()            鎖定memory模式下的回調函數的上下文和參數

  locked()             判斷回調函數列表是否已被鎖定

  fireWith(content,args)    以content爲上下文,args爲上下文,執行全部函數列表

  fire(arguments)       指定上下文爲當前回調函數列表來調用fireWith

  fired()           經過檢測變量memory的值來判斷回調函數列表是否被觸發過

這些方法概括起來就是新增/移除/觸發回調函數,還有幾個是查看當前的狀態的(是否已觸發、是否被禁用等)

$.Callbacks()能夠傳入任意組合,也能夠爲空,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
    <script>
        function fn1(val){console.log('fn1 says:' + val);}
        function fn2(val){console.log('fn2 says ' + val);}
        function fn3(val){console.log('fn3 says ' + val);}    
        
        var cbs = $.Callbacks();        //建立Callbacks對象
        cbs.add([fn1,fn2,fn3]);            //添加函數到回調函數列表中
        cbs.fire('test1');                //觸發回調函數,參數是test1    輸出:fn1 says:test一、fn2 says:test1和fn3 says:test
        cbs.remove(fn1,fn3);            //移除回調函數fn1,fn3
        cbs.fire('test2');                //觸發回調函數,輸出:fn2 says test2
    </script>
</body>
</html>

輸出以下:

 

 

這裏咱們定義了一個Callback(),沒有傳入任何參數,比較經常使用的是once和memory的組合,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
    <script>
        function fn1(val){console.log('fn1 says:' + val);}
        function fn2(val){console.log('fn2 says ' + val);}
        function fn3(val){console.log('fn3 says ' + val);}
        
        var cbs = $.Callbacks('once memory');
        cbs.add([fn1,fn2]);            
        cbs.fire('test');        //輸出:fn1 says:test和fn2 says:test
        cbs.fire('test');        //沒有輸出,由於設置了once標誌,當調用cbs.fire('test')後就把list清空了
        cbs.add(fn3);    
    </script>
</body>
</html>

輸出以下:

若是傳入once+memory的組合,這時回調函數被觸發後再調用fire()去觸發時是不會執行回調函數了的,由於當第一次fire()觸發回調函數後,若是由once標記就把內部的list設爲了undefined,能夠理解爲把list給禁用了,加入了memory標記的話當執行fire()時jQuery內部會把當時的上下文和參數保存起來,這樣下次直接添加回調函數就會自動回掉函數了

 

 源碼分析


writer by:大沙漠 QQ:22969969

$.Callbacks()的實現原理很簡單,Callbacks是jQuery內部的一個函數,第一次執行時該函數會把傳入的標記給緩存起來,經過做用域保存在內部的一個變量中,以後調用add()添加函數時也會一個個的緩存起來,最後調用fire()觸發回調函數時會遍歷這些回調函數列表,一個個的去觸發,中間根據不一樣的標記作不一樣的處理。

$.Callbacks是直接定義在內部的jQuery上的,大體以下

jQuery.Callbacks = function( flags ) {                    //在jQuery上添加一個Callbacks方法    

    // Convert flags from String-formatted to Object-formatted
    // (we check in cache first)
    flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};            //先嚐試從緩存對象flagsCache中獲取標記字符串flags對應的標記對象。若是沒找到再調用工具函數createFlags()建立標記

    var // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = [],
        // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list is currently firing
        firing,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // Add one or several callbacks to the list
        add = function( args ) {
            /*add 是一個工具函數,用於添加回調函數*/
        },
        // Fire callbacks
        fire = function( context, args ) {
            /*fire也是一個工具函數,用於觸發回調函數列表*/
        },
        // Actual Callbacks object
        self = {
            /*對象的定義*/
        };

    return self;                                                    //最後返回self,這是一個對象,該對象內定義的屬性也就是對外的接口,供咱們使用的
};

createFlags用於將字符串格式的標記轉換爲對象格式的標記,以下:

function createFlags( flags ) {                        //將字符串格式的標記轉換爲對象格式的標記
    var object = flagsCache[ flags ] = {},                    //初始化object和flagsCache[flags]爲空對象
        i, length;
    flags = flags.split( /\s+/ );                            //對flags用空格分隔
    for ( i = 0, length = flags.length; i < length; i++ ) {    //遍歷每一個標記
        object[ flags[i] ] = true;                                //屬性值一概設爲true,這裏訪問對象用方括號表示法,能夠用變量來表示對象的屬性。
    }
    return object;
}

以上面的第二個例子爲例(once+memory),執行到這裏返回後對應的以下:

 而後返回內部的self對象,這樣$.Callbacks()就執行完畢了,後面咱們調用add添加回調函數時會執行self內的add函數,以下:

add: function() {                    //添加回調函數
    if ( list ) {
        var length = list.length;
        add( arguments );                                    //用工具函數add添加回調函數
        // 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, unless previous
        // firing was halted (stopOnFalse)
        } else if ( memory && memory !== true ) {
            firingStart = length;
            fire( memory[ 0 ], memory[ 1 ] );
        }
    }
    return this;
},

這裏紅色標記的add是在self對象同做用域的add,這是一個工具函數,以下:

add = function( args ) {            //添加一個或多個回調函數到數組list中
    var i,
        length,
        elem,
        type,
        actual;
    for ( i = 0, length = args.length; i < length; i++ ) {        //遍歷參數
        elem = args[ i ];                                            //第一個參數的內容
        type = jQuery.type( elem );                                    //第一個參數的類型    ;是array或者function
        if ( type === "array" ) {                                    //若是參數是數組
            // Inspect recursively        
            add( elem );                                                //則遞歸調用自身
        } else if ( type === "function" ) {                            //若是參數是函數
            // Add if not in unique mode and callback is not in
            if ( !flags.unique || !self.has( elem ) ) {                //若是不是unique模式(該模式一個函數只能添加一次,就是回調函數列表中沒有重複值),或者 是unique模式但未添加過該函數。
                list.push( elem );                                        //添加args[i]到數組list中
            }
        }
    }
},

最後會push到list中,也就是上一層做用域的list中,例子裏執行到這裏list中的數據以下:

這樣三個函數都被緩存起來了,最後調用fire()觸發回調函數時會執行self內的fire()函數,以下:

fire: function() {
    self.fireWith( this, arguments );            //指定上下文爲當前回調函數列表來調用fireWith(context, args)
    return this;
}

fire()會將當前this做爲參數,直接調用fireWith,fireWith以下:

fireWith: function( context, args ) {            //使用指定的上下文和參數觸發回調函數列表中的全部回調函數
    if ( stack ) {        
        if ( firing ) {                                    //若是回調函數正在執行當中
            if ( !flags.once ) {            
                stack.push( [ context, args ] );
            }
        } else if ( !( flags.once && memory ) ) {        //若是回調函數未在執行中,而且不是已經觸發過的once模式,
            fire( context, args );                            //調用工具函數fire(context, args )執行全部函數
        }
    }
    return this;
},

最後會調用fire,也就是self同做用域的工具fire函數,以下:

fire = function( context, args ) {            //使用指定的上下文context和參數args調用數組list中的回調函數
    args = args || [];
    memory = !flags.memory || [ context, args ];                //若是當前不是memory模式,則設置memory爲ture,表示當前函數回調函數列表被觸發過。若是當前回調函數是memory模式,設置momory爲[context,args],除了表示當前函數回調函數列表被觸發過,還能保存上下文和參數。
    firing = true;                                                //把firing設爲ture,表示回調函數正在執行當中
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for ( ; list && firingIndex < firingLength; firingIndex++ ) {                        //若是沒有禁用列表則循環執行每一個函數
        if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {    //執行list數組裏的每個函數,若是開啓了stopOnFalse標記且有一個回調函數返回false
            memory = true; // Mark as halted                                                    //則把memory設置爲ture(清除上下文),且退出餘下全部函數列表的執行。
            break;
        }
    }
    firing = false;
    if ( list ) {                                                //若是沒有禁用列表
        if ( !flags.once ) {                                        //若是不是once模式,便可屢次觸發回調函數列表
            if ( stack && stack.length ) {
                memory = stack.shift();
                self.fireWith( memory[ 0 ], memory[ 1 ] );
            }
        } else if ( memory === true ) {                                //若是(是once模式,並且不是memory模式) 或者 (是once+memory 且設置了stopOnFlase模式,而且某個回調函數返回了false)
            self.disable();                                                //則禁用回調函數列表
        } else {                                                    //若是是once模式+memory模式
            list = [];                                                    //則清空數組list,後續添加的回調函數還會當即執行。
        }
    }
},

fire()執行完後回調函數列表就執行完畢了,中間經過一些標記作處理,若是由傳入memory,則會保存上下文,下次經過add添加回調函數時會當即執行的

相關文章
相關標籤/搜索