天天看點

jquery3.0源碼解讀(四)Callbacks

本來想先分析defer的,但是在defer的檔案中,發現它依賴于另外一個比較重要的子產品callbacks。于是,這節就先分析callbacks吧。

前置知識

  • 觀察者模式
  • 鈎子
  • 遞延

應用場景

在js開發中,由于沒有多線程,經常會遇到回調這個概念,比如說,在 ready 函數中注冊回調函數,注冊元素的事件處理等等。在比較複雜的場景下,當一個事件發生的時候,可能需要同時執行多個回調方法,可以直接考慮到的實作就是實作一個隊列,将所有事件觸發時需要回調的函數都加入到這個隊列中儲存起來,當事件觸發的時候,從這個隊列中依次取出儲存的函數并執行。

功能說明

callbacks指一個多用途的回調清單對象,提供了強大的的方式來管理回調函數清單。簡單的使用方式如下:

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

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}

var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!

callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!
           

簡單來說就是管理回調函數的執行的。這個子產品主要還是提供給jquery内部的ajax和defer使用。

$.callbacks(flags)

$.callbacks(flags)中的flags是$.Callbacks()的一個可選參數, 結構為一個用空格标記分隔的标志可選清單,用來改變回調清單中的行為 (比如. $.Callbacks( ‘unique stopOnFalse’ ))。

可用的 flags:

  • once: 確定這個回調清單隻執行一次
  • memory: 當隊列已經觸發之後,再添加進來的函數就會直接被調用(并且使用的是上一次fire的參數),不需要再觸發一次。
  • unique: 保證函數的唯一。
  • stopOnFalse: 當一個回調傳回false 時中斷調用

方法說明

  1. callbacks.add() 回調清單中添加一個回調或回調的集合。
  2. callbacks.disable() 禁用回調清單中的回調
  3. callbacks.disabled() 确定回調清單是否已被禁用。
  4. callbacks.empty() 從清單中删除所有的回調.
  5. callbacks.fire() 用給定的參數調用所有的回調
  6. callbacks.fired() 通路給定的上下文和參數清單中的所有回調。
  7. callbacks.fireWith() 通路給定的上下文和參數清單中的所有回調。
  8. callbacks.has() 确定清單中是否提供一個回調
  9. callbacks.lock() 鎖定目前狀态的回調清單。
  10. callbacks.locked() 确定回調清單是否已被鎖定。
  11. callbacks.remove() 從回調清單中的删除一個回調或回調集合。

源碼分析

源碼如下:

jQuery.Callbacks = function( options ) {

    options = typeof options === "string" ?
        createOptions( options ) :
        jQuery.extend( {}, options );

    var firing,

        memory,

        fired,

        locked,

        list = [],

        queue = [],

        firingIndex = -,

        fire = function() {

            locked = options.once;

            fired = firing = true;
            for ( ; queue.length; firingIndex = - ) {
                memory = queue.shift();
                while ( ++firingIndex < list.length ) {

                    if ( list[ firingIndex ].apply( memory[  ], memory[  ] ) === false &&
                        options.stopOnFalse ) {

                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            if ( !options.memory ) {
                memory = false;
            }

            firing = false;

            if ( locked ) {

                if ( memory ) {
                    list = [];

                } else {
                    list = "";
                }
            }
        },

        self = {

            add: function() {
                if ( list ) {

                    if ( memory && !firing ) {
                        firingIndex = list.length - ;
                        queue.push( memory );
                    }

                    ( function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            if ( jQuery.isFunction( arg ) ) {
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

                                // Inspect recursively
                                add( arg );
                            }
                        } );
                    } )( arguments );

                    if ( memory && !firing ) {
                        fire();
                    }
                }
                return this;
            },

            remove: function() {
                jQuery.each( arguments, function( _, arg ) {
                    var index;
                    while ( ( index = jQuery.inArray( arg, list, index ) ) > - ) {
                        list.splice( index,  );

                        // Handle firing indexes
                        if ( index <= firingIndex ) {
                            firingIndex--;
                        }
                    }
                } );
                return this;
            },

            has: function( fn ) {
                return fn ?
                    jQuery.inArray( fn, list ) > - :
                    list.length > ;
            },

            empty: function() {
                if ( list ) {
                    list = [];
                }
                return this;
            },

            disable: function() {
                locked = queue = [];
                list = memory = "";
                return this;
            },
            disabled: function() {
                return !list;
            },

            lock: function() {
                locked = queue = [];
                if ( !memory && !firing ) {
                    list = memory = "";
                }
                return this;
            },
            locked: function() {
                return !!locked;
            },

            fireWith: function( context, args ) {
                if ( !locked ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    queue.push( args );
                    if ( !firing ) {
                        fire();
                    }
                }
                return this;
            },

            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },

            fired: function() {
                return !!fired;
            }
        };

    return self;
};
           

下面,我們就直接從構造方法和傳回self中提供的操作方法,按照上面的操作方法清單順序依次分析。

$.callbacks(flags)

取出了options(flag)并儲存,并定義了若幹參數,分别是:

  • firing:是否正在fire觸發階段,用來判斷是外部的觸發,還是回調函數内部的嵌套觸發
  • memory:記錄上次觸發時使用的參數(相當于執行環境)
  • fired:記錄是否已經被觸發過至少一次
  • locked:鎖定外部fire相關接口
  • list:回調清單
  • queue:多次fire調用(因為可能被嵌套調用)的調用參數清單(相當于執行環境清單)
  • firingIndex:回調清單list的觸發索引,也會用在指定add遞延觸發位置

還有一個fire内部方法,這個比較重要,它的執行過程如下:

首先,locked = options.once;也就是說,如果flag中有once選項,那麼隻要執行過fire,下一次就會鎖住。

接着,fired = firing = true;标示正在執行fire。

之後,循環從queue中取出執行執行環境,指派給memory(儲存了目前執行環境)。并不斷順序執行清單中的回調函數。期間還判斷了stopOnFalse選項。當回調傳回false時,并且有stopOnFalse選項,就會銷毀memory,相當于memory選項沒有了。

再接着,判斷options.memory是否定義,如果沒有,同樣銷毀memory(上一次的執行環境)。

最後,判斷鎖定,如果鎖定,外部fire就不用了,由是否有遞延指定add(會調用内部fire)是否可用,無遞延就要disable掉(locked+list)。

最後,傳回self,self中暴露了各種操作方法,如下。

callbacks.add()

首先,判斷list(回調函數清單),實作了once功能。在上述fire函數中,如果有once标志,list将被指派成空。

接着,如果有memory(記錄着上次觸發時使用的參數),并且不在執行的話,儲存firingIndex(回調清單list的觸發索引,也會用在指定add遞延觸發位置),并且queue(多次fire調用(因為可能被嵌套調用)的調用參數清單)存入memory。

接着,把add的回調函數,壓入list,這裡支援遞歸add。

最後,如果不在firing中,并且是memory模式,則執行fire,由于之前queue中壓入了memory,并記錄了firingIndex,是以fire會執行剛剛被add進list的回調函數。如果在firing中,目前的函數,自然也會被執行。

callbacks.disable()

這個比較簡單,把locked ,queue,list,memory都清空,add将無法使用,fire同樣不行。

callbacks.disabled()

直接傳回!list。

callbacks.empty()

清空list 。

callbacks.fire()

直接調用了fireWith,并傳入了參數。

callbacks.fired()

傳回!!fired(雙感歎号指強制轉換成布爾值)。

callbacks.fireWith()

判斷是否locked,如果不是,把參數格式化成[環境,參數數組],寫進queue。并執行fire。

callbacks.has()

判斷是否有指定回調,無參數則判斷回調清單是否空。

callbacks.lock()

無遞延(每次執行完memory重置為false)或沒觸發過,則直接禁用;否則還是可以相應add後的遞延函數。

callbacks.locked()

傳回!!locked。

callbacks.remove()

移除回調,支援多參數。去掉所有相同回調,當回調内調用remove時,若删除項為已執行項,修正了firingIndex位置。

參考

  • http://www.cnblogs.com/aaronjs/p/3342344.html
  • http://www.cnblogs.com/haogj/p/4473477.html
  • http://blog.csdn.net/vbdfforever/article/details/50986673
  • http://www.cnblogs.com/yangjunhua/p/3509342.html