異步隊列用於實現異步任務和回調函數的解耦,爲ajax模塊、隊列模塊、ready事件提供基礎功能,包含三個部分:Query.Callbacks(flags)、jQuery.Deferred(funct)和jQuery.when()。本節講解Callbacks,也就是回調函數列表html
回調函數用於管理一組回調函數,支持添加、移除、觸發、鎖定和禁用回調函數,爲jQuery.ajax、jQuery.Deferred()和ready()事件提供基礎功能,咱們也能夠基於它編寫新的組件。jquery
使用方法:$.Callbacks(flags),flags是一個參數,用於指定傳遞的標記,能夠爲空,能夠設置爲如下四個選項之一,或者任意組合也能夠,以下:ajax
unique 確保一個回調函數只能被添加一次
stopOnFlase 當某個回調函數返回false時中斷執行
once 確保回調函數列表只能被觸發一次
memory 記錄上一次觸發回調函數列表時的參數,以後添加的任何函數都將用記錄的參數值當即調用數組
執行成功後返回一個對象,該函數含有以下幾個方法:緩存
add(fn/arr) 添加一個/多個回調函數到list數組中app
remove(fn1,fn2,fn3...) 從list中移除多個回調函數less
empty() 清空list數組異步
disable() 禁用列表,使他再也不作任何事情,該操做不可還原函數
disabled() 判斷是否已禁用列表,若是已經禁用了則返回true工具
lock() 鎖定memory模式下的回調函數的上下文和參數
locked() 判斷回調函數列表是否已被鎖定
fireWith(content,args) 以content爲上下文,args爲上下文,執行全部函數列表
fire(arguments) 指定上下文爲當前回調函數列表來調用fireWith
fired() 經過檢測變量memory的值來判斷回調函數列表是否被觸發過
這些方法概括起來就是新增/移除/觸發回調函數,還有幾個是查看當前的狀態的(是否已觸發、是否被禁用等)
$.Callbacks()能夠傳入任意組合,也能夠爲空,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script> </head> <body> <script> function fn1(val){console.log('fn1 says:' + val);} function fn2(val){console.log('fn2 says ' + val);} function fn3(val){console.log('fn3 says ' + val);} var cbs = $.Callbacks(); //建立Callbacks對象 cbs.add([fn1,fn2,fn3]); //添加函數到回調函數列表中 cbs.fire('test1'); //觸發回調函數,參數是test1 輸出:fn1 says:test一、fn2 says:test1和fn3 says:test cbs.remove(fn1,fn3); //移除回調函數fn1,fn3 cbs.fire('test2'); //觸發回調函數,輸出:fn2 says test2 </script> </body> </html>
輸出以下:
這裏咱們定義了一個Callback(),沒有傳入任何參數,比較經常使用的是once和memory的組合,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script> </head> <body> <script> function fn1(val){console.log('fn1 says:' + val);} function fn2(val){console.log('fn2 says ' + val);} function fn3(val){console.log('fn3 says ' + val);} var cbs = $.Callbacks('once memory'); cbs.add([fn1,fn2]); cbs.fire('test'); //輸出:fn1 says:test和fn2 says:test cbs.fire('test'); //沒有輸出,由於設置了once標誌,當調用cbs.fire('test')後就把list清空了 cbs.add(fn3); </script> </body> </html>
輸出以下:
若是傳入once+memory的組合,這時回調函數被觸發後再調用fire()去觸發時是不會執行回調函數了的,由於當第一次fire()觸發回調函數後,若是由once標記就把內部的list設爲了undefined,能夠理解爲把list給禁用了,加入了memory標記的話當執行fire()時jQuery內部會把當時的上下文和參數保存起來,這樣下次直接添加回調函數就會自動回掉函數了
源碼分析
writer by:大沙漠 QQ:22969969
$.Callbacks()的實現原理很簡單,Callbacks是jQuery內部的一個函數,第一次執行時該函數會把傳入的標記給緩存起來,經過做用域保存在內部的一個變量中,以後調用add()添加函數時也會一個個的緩存起來,最後調用fire()觸發回調函數時會遍歷這些回調函數列表,一個個的去觸發,中間根據不一樣的標記作不一樣的處理。
$.Callbacks是直接定義在內部的jQuery上的,大體以下
jQuery.Callbacks = function( flags ) { //在jQuery上添加一個Callbacks方法 // Convert flags from String-formatted to Object-formatted // (we check in cache first) flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; //先嚐試從緩存對象flagsCache中獲取標記字符串flags對應的標記對象。若是沒找到再調用工具函數createFlags()建立標記 var // Actual callback list list = [], // Stack of fire calls for repeatable lists stack = [], // Last fire value (for non-forgettable lists) memory, // 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, // Add one or several callbacks to the list add = function( args ) { /*add 是一個工具函數,用於添加回調函數*/ }, // Fire callbacks fire = function( context, args ) { /*fire也是一個工具函數,用於觸發回調函數列表*/ }, // Actual Callbacks object self = { /*對象的定義*/ }; return self; //最後返回self,這是一個對象,該對象內定義的屬性也就是對外的接口,供咱們使用的 };
createFlags用於將字符串格式的標記轉換爲對象格式的標記,以下:
function createFlags( flags ) { //將字符串格式的標記轉換爲對象格式的標記 var object = flagsCache[ flags ] = {}, //初始化object和flagsCache[flags]爲空對象 i, length; flags = flags.split( /\s+/ ); //對flags用空格分隔 for ( i = 0, length = flags.length; i < length; i++ ) { //遍歷每一個標記 object[ flags[i] ] = true; //屬性值一概設爲true,這裏訪問對象用方括號表示法,能夠用變量來表示對象的屬性。 } return object; }
以上面的第二個例子爲例(once+memory),執行到這裏返回後對應的以下:
而後返回內部的self對象,這樣$.Callbacks()就執行完畢了,後面咱們調用add添加回調函數時會執行self內的add函數,以下:
add: function() { //添加回調函數 if ( list ) { var length = list.length; add( arguments ); //用工具函數add添加回調函數 // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; },
這裏紅色標記的add是在self對象同做用域的add,這是一個工具函數,以下:
add = function( args ) { //添加一個或多個回調函數到數組list中 var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { //遍歷參數 elem = args[ i ]; //第一個參數的內容 type = jQuery.type( elem ); //第一個參數的類型 ;是array或者function if ( type === "array" ) { //若是參數是數組 // Inspect recursively add( elem ); //則遞歸調用自身 } else if ( type === "function" ) { //若是參數是函數 // Add if not in unique mode and callback is not in if ( !flags.unique || !self.has( elem ) ) { //若是不是unique模式(該模式一個函數只能添加一次,就是回調函數列表中沒有重複值),或者 是unique模式但未添加過該函數。 list.push( elem ); //添加args[i]到數組list中 } } } },
最後會push到list中,也就是上一層做用域的list中,例子裏執行到這裏list中的數據以下:
這樣三個函數都被緩存起來了,最後調用fire()觸發回調函數時會執行self內的fire()函數,以下:
fire: function() { self.fireWith( this, arguments ); //指定上下文爲當前回調函數列表來調用fireWith(context, args) return this; }
fire()會將當前this做爲參數,直接調用fireWith,fireWith以下:
fireWith: function( context, args ) { //使用指定的上下文和參數觸發回調函數列表中的全部回調函數 if ( stack ) { if ( firing ) { //若是回調函數正在執行當中 if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { //若是回調函數未在執行中,而且不是已經觸發過的once模式, fire( context, args ); //調用工具函數fire(context, args )執行全部函數 } } return this; },
最後會調用fire,也就是self同做用域的工具fire函數,以下:
fire = function( context, args ) { //使用指定的上下文context和參數args調用數組list中的回調函數 args = args || []; memory = !flags.memory || [ context, args ]; //若是當前不是memory模式,則設置memory爲ture,表示當前函數回調函數列表被觸發過。若是當前回調函數是memory模式,設置momory爲[context,args],除了表示當前函數回調函數列表被觸發過,還能保存上下文和參數。 firing = true; //把firing設爲ture,表示回調函數正在執行當中 firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { //若是沒有禁用列表則循環執行每一個函數 if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { //執行list數組裏的每個函數,若是開啓了stopOnFalse標記且有一個回調函數返回false memory = true; // Mark as halted //則把memory設置爲ture(清除上下文),且退出餘下全部函數列表的執行。 break; } } firing = false; if ( list ) { //若是沒有禁用列表 if ( !flags.once ) { //若是不是once模式,便可屢次觸發回調函數列表 if ( stack && stack.length ) { memory = stack.shift(); self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { //若是(是once模式,並且不是memory模式) 或者 (是once+memory 且設置了stopOnFlase模式,而且某個回調函數返回了false) self.disable(); //則禁用回調函數列表 } else { //若是是once模式+memory模式 list = []; //則清空數組list,後續添加的回調函數還會當即執行。 } } },
fire()執行完後回調函數列表就執行完畢了,中間經過一些標記作處理,若是由傳入memory,則會保存上下文,下次經過add添加回調函數時會當即執行的