本來想先分析defer的,但是在defer的檔案中,發現它依賴于另外一個比較重要的子產品callbacks。于是,這節就先分析callbacks吧。
前置知識
- 觀察者模式
- 鈎子
- 遞延
應用場景
在js開發中,由于沒有多線程,經常會遇到回調這個概念,比如說,在 ready 函數中注冊回調函數,注冊元素的事件處理等等。在比較複雜的場景下,當一個事件發生的時候,可能需要同時執行多個回調方法,可以直接考慮到的實作就是實作一個隊列,将所有事件觸發時需要回調的函數都加入到這個隊列中儲存起來,當事件觸發的時候,從這個隊列中依次取出儲存的函數并執行。
功能說明
callbacks指一個多用途的回調清單對象,提供了強大的的方式來管理回調函數清單。簡單的使用方式如下:
function fn1( value ){
console.log( value );
}
function fn2( value ){
fn1("fn2 says:" + value);
return false;
}
var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!
callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!
簡單來說就是管理回調函數的執行的。這個子產品主要還是提供給jquery内部的ajax和defer使用。
$.callbacks(flags)
$.callbacks(flags)中的flags是$.Callbacks()的一個可選參數, 結構為一個用空格标記分隔的标志可選清單,用來改變回調清單中的行為 (比如. $.Callbacks( ‘unique stopOnFalse’ ))。
可用的 flags:
- once: 確定這個回調清單隻執行一次
- memory: 當隊列已經觸發之後,再添加進來的函數就會直接被調用(并且使用的是上一次fire的參數),不需要再觸發一次。
- unique: 保證函數的唯一。
- stopOnFalse: 當一個回調傳回false 時中斷調用
方法說明
- 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 ) {
options = typeof options === "string" ?
createOptions( options ) :
jQuery.extend( {}, options );
var firing,
memory,
fired,
locked,
list = [],
queue = [],
firingIndex = -,
fire = function() {
locked = options.once;
fired = firing = true;
for ( ; queue.length; firingIndex = - ) {
memory = queue.shift();
while ( ++firingIndex < list.length ) {
if ( list[ firingIndex ].apply( memory[ ], memory[ ] ) === false &&
options.stopOnFalse ) {
firingIndex = list.length;
memory = false;
}
}
}
if ( !options.memory ) {
memory = false;
}
firing = false;
if ( locked ) {
if ( memory ) {
list = [];
} else {
list = "";
}
}
},
self = {
add: function() {
if ( list ) {
if ( memory && !firing ) {
firingIndex = list.length - ;
queue.push( memory );
}
( function add( args ) {
jQuery.each( args, function( _, arg ) {
if ( jQuery.isFunction( arg ) ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
// Inspect recursively
add( arg );
}
} );
} )( arguments );
if ( memory && !firing ) {
fire();
}
}
return this;
},
remove: function() {
jQuery.each( arguments, function( _, arg ) {
var index;
while ( ( index = jQuery.inArray( arg, list, index ) ) > - ) {
list.splice( index, );
// Handle firing indexes
if ( index <= firingIndex ) {
firingIndex--;
}
}
} );
return this;
},
has: function( fn ) {
return fn ?
jQuery.inArray( fn, list ) > - :
list.length > ;
},
empty: function() {
if ( list ) {
list = [];
}
return this;
},
disable: function() {
locked = queue = [];
list = memory = "";
return this;
},
disabled: function() {
return !list;
},
lock: function() {
locked = queue = [];
if ( !memory && !firing ) {
list = memory = "";
}
return this;
},
locked: function() {
return !!locked;
},
fireWith: function( context, args ) {
if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
queue.push( args );
if ( !firing ) {
fire();
}
}
return this;
},
fire: function() {
self.fireWith( this, arguments );
return this;
},
fired: function() {
return !!fired;
}
};
return self;
};
下面,我們就直接從構造方法和傳回self中提供的操作方法,按照上面的操作方法清單順序依次分析。
$.callbacks(flags)
取出了options(flag)并儲存,并定義了若幹參數,分别是:
- firing:是否正在fire觸發階段,用來判斷是外部的觸發,還是回調函數内部的嵌套觸發
- memory:記錄上次觸發時使用的參數(相當于執行環境)
- fired:記錄是否已經被觸發過至少一次
- locked:鎖定外部fire相關接口
- list:回調清單
- queue:多次fire調用(因為可能被嵌套調用)的調用參數清單(相當于執行環境清單)
- firingIndex:回調清單list的觸發索引,也會用在指定add遞延觸發位置
還有一個fire内部方法,這個比較重要,它的執行過程如下:
首先,locked = options.once;也就是說,如果flag中有once選項,那麼隻要執行過fire,下一次就會鎖住。
接着,fired = firing = true;标示正在執行fire。
之後,循環從queue中取出執行執行環境,指派給memory(儲存了目前執行環境)。并不斷順序執行清單中的回調函數。期間還判斷了stopOnFalse選項。當回調傳回false時,并且有stopOnFalse選項,就會銷毀memory,相當于memory選項沒有了。
再接着,判斷options.memory是否定義,如果沒有,同樣銷毀memory(上一次的執行環境)。
最後,判斷鎖定,如果鎖定,外部fire就不用了,由是否有遞延指定add(會調用内部fire)是否可用,無遞延就要disable掉(locked+list)。
最後,傳回self,self中暴露了各種操作方法,如下。
callbacks.add()
首先,判斷list(回調函數清單),實作了once功能。在上述fire函數中,如果有once标志,list将被指派成空。
接着,如果有memory(記錄着上次觸發時使用的參數),并且不在執行的話,儲存firingIndex(回調清單list的觸發索引,也會用在指定add遞延觸發位置),并且queue(多次fire調用(因為可能被嵌套調用)的調用參數清單)存入memory。
接着,把add的回調函數,壓入list,這裡支援遞歸add。
最後,如果不在firing中,并且是memory模式,則執行fire,由于之前queue中壓入了memory,并記錄了firingIndex,是以fire會執行剛剛被add進list的回調函數。如果在firing中,目前的函數,自然也會被執行。
callbacks.disable()
這個比較簡單,把locked ,queue,list,memory都清空,add将無法使用,fire同樣不行。
callbacks.disabled()
直接傳回!list。
callbacks.empty()
清空list 。
callbacks.fire()
直接調用了fireWith,并傳入了參數。
callbacks.fired()
傳回!!fired(雙感歎号指強制轉換成布爾值)。
callbacks.fireWith()
判斷是否locked,如果不是,把參數格式化成[環境,參數數組],寫進queue。并執行fire。
callbacks.has()
判斷是否有指定回調,無參數則判斷回調清單是否空。
callbacks.lock()
無遞延(每次執行完memory重置為false)或沒觸發過,則直接禁用;否則還是可以相應add後的遞延函數。
callbacks.locked()
傳回!!locked。
callbacks.remove()
移除回調,支援多參數。去掉所有相同回調,當回調内調用remove時,若删除項為已執行項,修正了firingIndex位置。
參考
- http://www.cnblogs.com/aaronjs/p/3342344.html
- http://www.cnblogs.com/haogj/p/4473477.html
- http://blog.csdn.net/vbdfforever/article/details/50986673
- http://www.cnblogs.com/yangjunhua/p/3509342.html