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