工具方法。對函數的統一管理。html
jquery2.0.3版本$.Callback()部分的源碼以下:jquery
// String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; } /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) 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 = 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 ) { memory = false; // To prevent further calls using add break; } } firing = false; if ( list ) { if ( stack ) { if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { list = []; } else { self.disable(); } } }, // Actual Callbacks object 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, 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? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away } else if ( 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( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes 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; };
觀察者模式,添加完後統一觸發。數組
function aaa(){ alert(1); } function bbb(){ alert(2); } var cb= $.Callbacks(); cb.add(aaa); cb.add(bbb); cb.fire();
要統一的管理aaa和bbb。有時候以下,很難對不一樣做用域下的函數進行統一管理。數據結構
function aaa(){ alert(1); } (function(){ function bbb(){ alert(2); } })(); aaa(); bbb();
只能彈出1,由於bbb是局部做用域中的。app
$callback能夠作到。以下,只要cb是全局的。ide
var cb= $.Callbacks(); function aaa(){ alert(1); } cb.add(aaa); (function(){ function bbb(){ alert(2); } cb.add(bbb); })(); cb.fire();
對應複雜狀況頗有用。統一管理,經過fire統一觸發。函數
Callback接收一個參數,能夠有4個選項,once,memory,unique,stopOnFalse。工具
self單體有這些方法:add,remove,has,empty,disable,disabled,lock,locked, fireWith,fire,fired。oop
list=[]數組變量,用來收集回調函數。fire的時候對其循環調用。學習
add:push數組
fire:調用fireWith,fireWith容許傳參,fire可傳可不傳。
fireWith:調用私有函數fire,在私有函數fire中for循環list。
remove:splice數組。
4個參數:
舉例:
不傳參數,fire幾回就觸發幾回。
function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks(); cb.add(aaa); cb.add(bbb); cb.fire(); //1 2 cb.fire();//1 2
function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks('once'); cb.add(aaa); cb.add(bbb); cb.fire(); //1 2 cb.fire();
不傳參數,在fire以後add的回調不能被fire。
//不寫參數,只彈出1,2不會彈出 function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks(); cb.add(aaa); cb.fire(); //1 cb.add(bbb);
function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks('memory'); cb.add(aaa); cb.fire(); //1 2 cb.add(bbb);
//不加參數,add2次aaa,就會觸發2次aaa function aaa() { alert(1); } var cb = $.Callbacks(); cb.add(aaa); cb.add(aaa); cb.fire(); //1 1
function aaa() { alert(1); } var cb = $.Callbacks('unique'); cb.add(aaa); cb.add(aaa); cb.fire(); //1 加了unique參數,一樣的函數不能屢次add
function aaa() { alert(1); return false; } function bbb() { alert(2); } var cb = $.Callbacks(); cb.add(aaa); cb.add(bbb); cb.fire(); //1 2 不傳參,第一個函數返回false時後面的函數也能正常執行
function aaa() { alert(1); return false; } function bbb() { alert(2); } var cb = $.Callbacks('stopOnFalse'); cb.add(aaa); cb.add(bbb); cb.fire(); //1 //傳參stopOnFalse,第一個函數返回false時後面的函數再也不執行
function aaa() { alert(1); } function bbb() { alert(2); } //組合使用,只執行一次,而且彈出1 2 var cb = $.Callbacks('once memory'); cb.add(aaa); cb.fire(); //1 cb.add(bbb); cb.fire();
源碼中:
傳入了 once和memory後,
options={once:true,memory:true} optionCache={ "once memory":{once:true,memory:true} }
參數做爲每一個回調函數的實參
function aaa(n) { alert("aaa "+n); } function bbb(n) { alert("bbb "+n); } var cb = $.Callbacks(); cb.add(aaa); cb.add(bbb); //fire傳參 cb.fire("hello"); //彈出aaa hello 和bbb hello
Callbacks就是一個工具函數,內部定義了一個self ,add和remove還有has等掛在self上。
$.Callbacks有4個可選的參數,能夠組合傳入,用空格分隔。好比 $.Callbacks("once memory unique");
這樣傳入的構造函數字符串其實是一個字符串,源碼中作了處理會把這個字符串轉成對象。
// String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; }
在構造函數中傳入一個options後,先進行以下處理調用。把一個字符串處理成一個對象。
傳入的options="once memory unique"處理後options={once:true,memory:true,unique:true}。
// Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? ( optionsCache[ options ] || createOptions( options ) ) : jQuery.extend( {}, options );
過程以下:options="once memory unique"是string類型,因此先從optionsCache中獲取,如今optionsCache爲{}因此optionsCache[ options ]是undefined走後面的createOptions( options ) 。create操做中先新建一個以options爲鍵的空對象,再循環給對象中填充。循環操做完
optionCache爲
optionCache={ "once memory unique":{once:true,memory:true,unique:true} }
options爲
options={once:true,memory:true,unique:true}
主要是把回調函數Push到數組list中。
add: function() { if ( list ) { //list初始化爲[],if判斷會返回true // First, we save the current length var start = list.length; (function add( args ) { jQuery.each( args, function( _, arg ) { ////處理cb.add(aaa,bbb)這種調用 var type = jQuery.type( arg );//arg就是每個函數 if ( type === "function" ) {//arg是函數就push到list中,此時有個判斷有沒有unique if ( !options.unique || !self.has( arg ) ) {//有unique走後面,判斷list中有沒有這個函數,有就不添加了 list.push( arg ); } } else if ( arg && arg.length && type !== "string" ) { //處理cb.add([aaa,bbb])這種調用 // Inspect recursively add( arg );//遞歸分解,最終仍是push到list } }); })( arguments ); // 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 } else if ( 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( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 );//主要就是splice刪除操做 // Handle firing indexes if ( firing ) { if ( index <= firingLength ) { firingLength--; } if ( index <= firingIndex ) { firingIndex--; } } } }); } return this; },
self的fire調用self的fireWith,fireWith把參數傳遞到fire()函數。
// 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; },
fire()時主要是for循環
// Fire callbacks fire = function( data ) { memory = options.memory && data; fired = true;//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 ) {//每次函數調用同時處理stopOnFalse的狀況 memory = false; // To prevent further calls using add //stopOnFalse後有memory也很差使了 break; } } firing = false;//觸發結束 if ( list ) { if ( stack ) { if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { list = []; } else { self.disable(); } } },
好比在 fire 處理隊列中,某個函數又在隊列中添加了一個回調函數,或者,在隊列中又刪除了某個回調函數。 fire 處理過程當中,某個函數又調用了 fire 來觸發事件呢?
先經過例子來看一下效果
function aaa() { alert(1); cb.fire(); //在這裏調用fire()會出現什麼問題 死循環 } function bbb() { alert(2); } var cb = $.Callbacks(); cb.add(aaa); cb.add(bbb); cb.fire();
在執行函數的過程當中再次調用fire()的執行順序是怎樣的?
var bBtn=true;//用bBtn避免死循環 function aaa() { alert(1); if(bBtn){ cb.fire();//注意這裏fire調用後執行順序是1 2 1 2,而不是1 1 2 2 bBtn=false; } } function bbb() { alert(2); } var cb = $.Callbacks(); cb.add(aaa); cb.add(bbb); cb.fire();
結論:把函數運行過程當中觸發的fire()放到了運行過程的隊列當中。
fire 處理過程當中,某個函數又調用了 fire 來觸發事件時,jQuery的處理方式以下:
將這個嵌套的事件先保存起來,等到當前的回調序列處理完成以後,再檢查被保存的事件,繼續完成處理。顯然,使用隊列是處理這種狀況的理想數據結構,若是遇到這種情況,咱們就將事件數據入隊,待處理的時候,依次出隊數據進行處理。何時須要這種處理呢?顯然不是once的狀況。在JavaScript中,堆隊列也是經過數組來實現的,push用來將數據追加到數組的最後,而shift用來出隊,從數據的最前面獲取數據。
不過,jQuery沒有稱之爲隊列,而是取名stack。
// Stack of fire calls for repeatable lists stack = !options.once && [],
入隊
源碼中,在fireWith的時候判斷for循環有沒有執行完
fireWith: function( context, args ) { ...if ( firing ) {//firing在for循環沒有走完時一直是true stack.push( args );//因此這句話意思就是函數執行時再去fire()調用就會push到stack數組中 } else { fire( args ); } } return this; },
出隊
再去調用fire()的時候
// Fire callbacks fire = function( data ) { memory = options.memory && data; fired = true;//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 ) {//每次函數調用同時處理stopOnFalse的狀況 memory = false; // To prevent further calls using add //stopOnFalse後有memory也很差使了 break; } } firing = false;//觸發結束 if ( list ) { if ( stack ) { //這就是出如今函數執行過程當中再次fire()的時候,等循環執行完,再去按順序執行 if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) {//只執行一次的時候,有once,memory就清空list,此時fire()就至關於一個執行一個空數組 list = []; } else { self.disable();//disable阻止後續任何的fire()操做 } } },
針對下面這段源碼的一個例子:
once和memory同時存在的時候,fire()無效由於list爲[]了,可是add仍然有效。
當有memory的時候,把以前添加的清空;容許添加並再次運行fire後清空;當不存在memory的時候既只有once配置,fire以後既不容許作任何操做了。
else if ( memory ) {//只執行一次的時候,有once,memory就清空list,此時fire()就至關於一個執行一個空數組 list = []; } else { self.disable();//disable阻止後續任何的fire()操做 }
disable阻止後續任何的fire()操做。
function aaa() { alert(1); } function bbb() { alert(2); } //組合使用,只執行一次,而且彈出1 2 3 var cb = $.Callbacks('once memory'); cb.add(aaa); cb.fire(); //1 cb.fire();//此時list爲[] cb.add(bbb); cb.fire(); function ccc(){ alert(3); } cb.add(ccc);
has(fn):判斷list有沒有fn
empty: 清空數組list=[]
disable:所有鎖住,禁止了,以下
// Have the list do nothing anymore disable: function() { list = stack = memory = undefined; return this; },
disabled:判斷是否是禁止了。return !list;
lock:只是把stack鎖住
// Lock the list in its current state lock: function() { stack = undefined; if ( !memory ) { self.disable(); } return this; },
locked:是否locked。 return !stack;
disable禁止全部操做
function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks('memory'); cb.add(aaa); cb.fire(); //1 cb.disable();//disable()後只能彈出1 由於禁止全部操做了,雖然有Memory cb.add(bbb);//不起做用了,此時list變爲undefined了 cb.fire();//不起做用了
lock只是鎖住數組
function aaa() { alert(1); } function bbb() { alert(2); } var cb = $.Callbacks('memory'); cb.add(aaa); cb.fire(); //1 2 cb.lock();//lock()只是把後續的fire()鎖住,其餘操做是鎖不住的 cb.add(bbb); cb.fire();//不起做用了 此時list爲[]
參考:
http://www.cnblogs.com/haogj/p/4473477.html
本文做者starof,因知識自己在變化,做者也在不斷學習成長,文章內容也不定時更新,爲避免誤導讀者,方便追根溯源,請諸位轉載註明出處:http://www.cnblogs.com/starof/p/6885500.html有問題歡迎與我討論,共同進步。