jQuery源碼學習(7)-Callbacks

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

1、使用場景:

var callbacks = $.Callbacks();

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

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

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

便捷的處理參數

  • 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(); //未執行

2、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()     從回調列表中的刪除一個回調或回調集合。

源碼分析:

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()的核心思想是 Pub/Sub 模式,建立了程序間的鬆散耦合和高效通信。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章