源碼API:http://api.jquery.com/jQuery.Callbacks/
jQuery.Callbacks()是在版本1.7中新加入的。它是一個多用途的回調函數列表對象,提供了一種強大的方法來管理回調函數隊列。
那么jQuery.Callbacks使用場景在哪里?
在很多時候需要控制一系列的函數順序執行。那么一般就需要一個隊列函數來處理這個問題
我們看一段代碼
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可以把函數加入到隊列末尾才執行的原理
*****但是這樣寫,是不是很麻煩?*****
我們換成jQuery提供的方式
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(); //未執行
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的返回
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: 路過留點痕。。