天天看點

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)。

繼續閱讀