源碼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的返回
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
類型: 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: 路過留點痕。。