jQuery 2.0.3 源碼分析 回調對象 - Callbacks

源碼API:http://api.jquery.com/jQuery.Callbacks/css

jQuery.Callbacks()是在版本1.7中新加入的。它是一個多用途的回調函數列表對象,提供了一種強大的方法來管理回調函數隊列。jquery

 

那麼jQuery.Callbacks使用場景在哪裏?git

在不少時候須要控制一系列的函數順序執行。那麼通常就須要一個隊列函數來處理這個問題github

咱們看一段代碼ajax

function Aaron(List, callback) {
    setTimeout(function() {
      var task = List.shift();
      task(); //執行函數
      if (task.length > 0) {  //遞歸分解
        setTimeout(arguments.callee, 1000)
      } else {
        callback()
      }
    }, 25)
  }

  Aaron([function(){
    alert('a')
  },function(){
    alert('b')
  }],function(){
    alert('callback')
  })
 
分別彈出 ‘a’ , ‘b’ ,’callback’

傳入一組函數參數,靠遞歸解析,分個執行,其實就是靠setTimeout能夠把函數加入到隊列末尾才執行的原理設計模式

*****可是這樣寫,是否是很麻煩?*****api

 

咱們換成jQuery提供的方式數組

var callbacks = $.Callbacks();

  callbacks.add(function() {
    alert('a');
  })

  callbacks.add(function() {
    alert('b');
  })

  callbacks.fire(); //輸出結果: 'a' 'b'

是否是便捷不少了,代碼又很清晰,因此它是一個多用途的回調函數列表對象,提供了一種強大的方法來管理回調函數隊列。promise

 

同時還提供幾個便捷的處理參數緩存

  • once: 確保這個回調列表只執行( .fire() )一次(像一個遞延 Deferred).
  • memory: 保持之前的值,將添加到這個列表的後面的最新的值當即執行調用任何回調 (像一個遞延 Deferred).
  • unique: 確保一次只能添加一個回調(因此在列表中沒有重複的回調).
  • stopOnFalse: 當一個回調返回false 時中斷調用
var callbacks = $.Callbacks('once');

  callbacks.add(function() {
    alert('a');
  })

  callbacks.add(function() {
    alert('b');
  })

  callbacks.fire(); //輸出結果: 'a' 'b'
  callbacks.fire(); //未執行

once的做用是使callback隊列只執行一次

OK,咱們大概知道這個是幹嗎用的了,能夠開始上正菜了。

 


$.Callbacks是在jQuery內部使用,如爲$.ajax,$.Deferred等組件提供基礎功能的函數,jQuery在1.5引入了Deferred對象(異步列隊),jQuery內部基本全部有異步的代碼都被promise所轉化成同步代碼執行了,後期在討論了

根據jQuery.Callbacks()的API

提供一下幾種方法:

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

 

咱們看官網提供的demo

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

能夠將上述兩個方法做爲回調函數,並添加到 $.Callbacks 列表中,並按下面的順序調用它們:

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

這樣作的結果是,當構造複雜的回調函數列表時,將會變動很簡單。能夠根據須要,很方面的就能夠向這些回調函數中傳入所需的參數。

上面的例子中,咱們使用了 $.Callbacks() 的兩個方法: .add().fire()。 .add() 能夠向回調函數列表中添加新的回調函數,fire() 能夠向回調函數中傳遞參數,並執行回調函數。

 

設計思想:

先看官網的demo這個列子,涉及到了 add 與 fire方法,熟悉設計模式的童鞋呢,一眼就看出,其實又是基於發佈訂閱的觀察者模式的設計了

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

做爲 $.Callbacks() 的建立組件的一個演示,只使用回調函數列表,就能夠實現 Pub/Sub 系統。將 $.Callbacks 做爲一個隊列

 

我來模擬下常規最簡單的實現

var Observable = {
      callbacks: [],
      add: function(fn) {
        this.callbacks.push(fn);
      },
      fire: function() {
        this.callbacks.forEach(function(fn) {
          fn();
        })
      }
  }

Observable.add(function() {
  alert(1)
})
Observable.fire(function() {
  alert(2)
})

Observable.fire(); // 1, 2

構建一個存放回調的數組,如this.callbacks= [] 添加回調時,將回調push進this.callbacks,執行則遍歷this.callbacks執行回調。

也彈出1跟2了,實際上jQuery.callbacks是如何處理的呢?

 


咱們看源碼
整個$.Callbacks的源碼不多,它是一個工廠函數,使用函數調用(非new,它不是一個類)建立對象,它有一個可選參數flags用來設置回調函數的行爲。、

對外的接口也就是self的返回

image

 

self上的add源碼

展開
源碼

其中有一段代碼要單獨拿出來

//這裏用了一個當即執行的add函數來添加回調
//直接遍歷傳過來的arguments進行push
(function add( args ) {
    jQuery.each( args, function( _, arg ) {
        var type = jQuery.type( arg );
        //若是所傳參數爲函數,則push
        if ( type === "function" ) {
            if ( !options.unique || !self.has( arg ) ) {  //當$.Callbacks('unique')時,保證列表裏面不會出現重複的回調
                list.push( arg );
            }
        } else if ( arg && arg.length && type !== "string" ) {  //假如傳過來的參數爲數組或array-like,則繼續調用添加,從這裏能夠看出add的傳參能夠有add(fn),add([fn1,fn2]),add(fn1,fn2)
            // Inspect recursively
            add( arg );
        }
    });
})( arguments )
 

add方法

實參能夠是Function, Array

callbacks.add( callbacks )
  • callbacks

    類型: Function, Array

    一個函數,或者一個函數數組,用來添加到回調列表。

若是是數組會遞歸調用私有的add函數 list.push( arg );

發現沒,設計的原理上其實跟上面發的簡單模式 大同小異

 

fire方法

外觀模式 self.fire –> self.fireWith –> fire

最終執行代碼是內部私有的fire方法了

fire方法

最終處理的代碼

list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )

其實就是拿出list中保存的回調函數,執行罷了,因此整個設計的原理,仍是符合咱們開始設想的

 


具體的實現

$.Callbacks( "once" )

確保這個回調列表只執行( .fire() )一次(像一個遞延 Deferred).

var callbacks = $.Callbacks( "once" );
callbacks.add( fn1 );
callbacks.fire( "foo" );  //foo 只執行了一次,後面沒執行了
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" ); 

在fire中調用了 self.disable(); 方法

// 禁用回調列表中的回調。
disable: function() {
    list = stack = memory = undefined;
    return this;
},

 


$.Callbacks( "memory" )

保持之前的值,將添加到這個列表的後面的最新的值當即執行調用任何回調 (像一個遞延 Deferred).

var callbacks = $.Callbacks("memory");

  callbacks.add(function() {
    console.log("f1");
  });

  callbacks.fire(); //輸出 "f1",這時函數列表已經執行完畢!

  callbacks.add(function() {
    console.log("f2");
  }); //memory做用在這裏,沒有fire,同樣有結果: f2

在調用 add() 方法時,若是這時 callbacks隊列 知足 fired && firing = false(真執行完畢) && memory(須要在構造函數指定),那麼add() 進去的回調函數會當即執行,而這個 add 進去的回調函數調用時的參數存儲在 memory 變量中。memory 變量用於存儲最後一次調用 callbacks.fireWith(...) 時所使用的參數 [context, arguments]。

 


$.Callbacks( "unique" )

確保一次只能添加一個回調(因此在列表中沒有重複的回調)

var f1 = function() {
          console.log("f1");
       };

       var callbacks = $.Callbacks();
       callbacks.add(f1);
       callbacks.add(f1);
       callbacks.fire();         //輸出  f1 f1

       //傳遞參數 "unique"
       callbacks = $.Callbacks("unique");
       callbacks.add(f1);     //有效
       callbacks.add(f1);     //添加不進去
       callbacks.fire();     //輸出: f1

****注意add方法默認不去重,好比這裏fn1添加兩次,fire時會觸發兩次****

這裏處理很簡單

if ( !options.unique || !self.has( arg ) ) { //確保是否能夠重複
    list.push( arg );
}                }

在添加的處處理隊列時候,判斷一下便可

 


$.Callbacks( "stopOnFalse" ):

當一個回調返回false 時中斷調用

var f1 = function() {
    console.log("f1");
    return false
  }; //注意 return false;
  var f2 = function() {
    console.log("f2");
  };

  var callbacks = $.Callbacks();
  callbacks.add(f1);
  callbacks.add(f2);
  callbacks.fire(); //輸出 f1 f2

  callbacks = $.Callbacks("memory stopOnFalse");
  callbacks.add(f1);
  callbacks.add(f2);
  callbacks.fire();     //只輸出  f1

  callbacks.add(function() {
    console.log("f3");
  }); //不會輸出,memory已經失去做用了
  callbacks.fire();     //從新觸發,輸出f1

 

附源碼:

jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    //經過字符串在optionsCache尋找有沒有相應緩存,若是沒有則建立一個,有則引用
    //若是是對象則經過jQuery.extend深複製後賦給options。
    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
        fired, // 列表中的函數是否已經回調至少一次

        // 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, // 當前正在回調的函數索引

        // Actual callback list
        list = [], // 回調函數列表

        // Stack of fire calls for repeatable lists
        stack = !options.once && [],// 可重複的回調函數堆棧,用於控制觸發回調時的參數列表

        // Fire callbacks// 觸發回調函數列表
        fire = function( data ) {
            //若是參數memory爲true,則記錄data
            memory = options.memory && data;
            fired = true; //標記觸發回調
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            //標記正在觸發回調
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    // 阻止將來可能因爲add所產生的回調
                    memory = false; // To prevent further calls using add
                    break; //因爲參數stopOnFalse爲true,因此當有回調函數返回值爲false時退出循環
                }
            }
            //標記回調結束
            firing = false;
            if ( list ) {
                if ( stack ) {
                    if ( stack.length ) {
                        //從堆棧頭部取出,遞歸fire
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//不然,若是有記憶
                    list = [];
                } else {//再不然阻止回調列表中的回調
                    self.disable();
                }
            }
        },
        // Actual Callbacks object
        // 暴露在外的Callbacks對象,對外接口
        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傳進來的列表的每個對象執行操做
                        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?
                    // 若是回調列表中的回調正在執行時,其中的一個回調函數執行了Callbacks.add操做
                    // 上句話能夠簡稱:若是在執行Callbacks.add操做的狀態爲firing時
                    // 那麼須要更新firingLength值
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we're not firing then
                    // we should call right away
                    } else if ( memory ) {
                        //若是options.memory爲true,則將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循環的意義在於藉助於強大的jQuery.inArray刪除函數列表中相同的函數引用(沒有設置unique的狀況)
                        // jQuery.inArray將每次返回查找到的元素的index做爲本身的第三個參數繼續進行查找,直到函數列表的盡頭
                        // splice刪除數組元素,修改數組的結構
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            // Handle firing indexes
                            // 在函數列表處於firing狀態時,最主要的就是維護firingLength和firgingIndex這兩個值
                            // 保證fire時函數列表中的函數可以被正確執行(fire中的for循環須要這兩個值
                            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 );
                    }
                }
                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;
};

jQuery.Callbacks() 比較簡單,也沒什麼難點

jQuery.Callbacks() 方法的核心是 fire() 方法,將該 fire() 方法做爲私有方法被封裝在函數中不可直接訪問

所以像 memory、firing、fired 這些狀態對於外部上下文來講是不可更改的

還有須要注意的是,若是回調函數中使用了 this 對象,能夠直接用這個 this 來訪問self對象的公有API。固然,也能夠用 fireWith() 本身指定 this 的引用對象。

jQuery.Callbacks()的核心思想是 Pub/Sub 模式,創建了程序間的鬆散耦合和高效通訊。

 

PS: 路過留點痕。。

相關文章
相關標籤/搜索