做爲參數傳遞給另外一個函數執行的函數咱們稱爲回調函數,那麼該回調又是不是異步的呢,何謂異步,如:做爲事件處理器,或做爲參數傳遞給html
(setTimeout,setInterval)這樣的異步函數,或做爲ajax發送請求,應用於請求各類狀態的處理,咱們能夠稱爲異步回調,jQuery.Callbacksajax
爲咱們封裝了一個回調對象模塊,咱們先來看一個應用場景:數組
// 爲何jQuery中的ready事件能夠執行多個回調,這得益於咱們的jQuery.Deferred遞延對象(是基於jQuery.Callbacks回調模塊) jQuery(function($) { console.log('document is ready!'); // do something }); jQuery(function($) { // do something }); // 實現原型 // jQuery.Deferred版代碼 var df = jQuery.Deferred(); df.resolve(); // 在ready事件中調用 // 能夠屢次執行綁定的函數 df.done(fn1, fn2, fn3); df.done(fn4); // ... // jQuery.Callbacks版代碼 var cb = jQuery.Callbacks('once memory'); cb.fire(); // 在ready事件中調用 // 能夠屢次執行綁定的函數 cb.add(fn1, fn2, fn3); cb.add(fn4); // ...
如今咱們知道jQuery中的ready事件是能夠這樣執行多個回調的,要想深刻理解其源碼,讓咱們繼續看下面吧緩存
jQuery回調、遞延對象總結篇索引:app
jQuery回調、遞延對象總結(上篇)—— jQuery.Callbacks異步
jQuery回調、遞延對象總結(中篇) —— 神奇的then方法函數
jQuery回調、遞延對象總結(下篇) —— 解密jQuery.when方法oop
使用一個私有變量list(數組)存儲回調,執行jQuery.Callbacks函數將返回一個能夠操做回調列表list的接口對象,
而傳入jQuery.Callbacks函數的options參數則用來控制返回的回調對象操做回調列表的行爲this
回調對象中的方法spa
{ add: 增長回調到list中 remove: 從list中移除回調 fire: 觸發list中的回調 fired: 回調對象是否執行過fire方法 fireWith: 觸發list中的回調,第一個參數爲執行域 has: 判斷函數是否在list中 empty: 將list致空,list = []; lock: 鎖定list locked: 是否鎖定 disable: 禁用回調對象 disabled: 是否禁用 }
參數標誌:
options = { once: 回調對象僅觸發(fire)一次 memory: 跟蹤記錄每一次傳遞給fire函數的參數,在回調對象觸發後(fired), 將最後一次觸發(fire)時的參數(value)傳遞給在add操做後即將被調用的回調 unique: 在add操做中,相同的函數僅只一次被添加(push)到回調列表中 stopOnFalse:當回調函數返回false,中斷列表中的回調循環調用,且memory === false,阻止在add操做中將要觸發的回調 }
var // Used for splitting on whitespace core_rnotwhite = /\S+/g; var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache // 將字符串格式選項轉化成對象格式形式,並存儲在緩存對象optionsCache[options]中 // 該緩存起做用適用於執行屢次jQuery.Callbacks函數,且傳遞options參數一致,咱們在jQuery.Deferred // 源碼就能夠看到tuples二維數組中執行了兩次jQuery.Callbacks('once memory') function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; } 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 // Flag to know if list is currently firing firing, // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired fired, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // First callback to fire (used internally by add and fireWith) firingStart, // 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; // 迭代list回調列表,列表中的回調被應用(或執行回調) for ( ; list && firingIndex < firingLength; firingIndex++ ) { // 若是回調返回false,且options.stopOnFlase === true,則中斷循環 // 注:data[1]是一個僞數組(self.fire方法中的arguments(參數集合)) if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { memory = false; // To prevent further calls using add // 阻止在add操做中可能執行的回調 break; } } firing = false; if ( list ) { // (options.once === undefined),回調對象能夠觸發屢次 if ( stack ) { // 處理正在執行的回調中的fireWith操做 // 注:若是執行的回調中真的擁有fire或fireWith操做,那麼列表中的回調將會無限循環的執行,請看實例1 if ( stack.length ) { fire( stack.shift() ); } } // (options.once === true && options.memory === true) // 回調列表致空,但容許add繼續添加並執行回調 else if ( memory ) { list = []; } // (options.once === true && options.memory === undefined) // 禁用回調對象 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" ) { // 回調不惟一 或 惟一且不存在,則push 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? // 若是正在執行的回調執行了add操做,更新firingLength,將列表中新加進來的最後一個回調加入到回調執行的隊列中 if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away // 若是可能(options.memory === true),在回調對象不能再次fire(options.once === true)時, // 咱們應該使用memory(記錄的最後一次fire時的參數)當即調用回調 } 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 // 在回調對象觸發(fire)時,若是firingLength、firingIndex(正在執行的回調在列表list中的索引index) // 大於等於 移除的回調的索引(index),分別減一,確保回調執行隊列中未執行的回調依次執行 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 // 將列表致空,list = []; firingLenght = 0; empty: function() { list = []; firingLength = 0; return this; }, // Have the list do nothing anymore // 禁用回調對象 // 將list賦值爲undefined就可使self中的add,remove,fire,fireWith方法中止工做 // 我認爲這裏把stack、memory賦值爲undefined與否是沒有任何關係的 disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state // 鎖定回調列表 // 若是(fired !== true || options.memory === false),則視爲禁用(disable) // 若是(fired === true && options.memory === true),則視爲options.once === true // 請看實例2 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 ) { // 回調對象未執行過fire 或且 能夠執行屢次(options.once === false) // 若是(fired === true && options.once === true),則不會執行fire操做 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; };
實例一、 處理回調函數中的fire,或fireWidth操做
var cb = jQuery.Callbacks(); var fn1 = function(arg){ console.log( arg + '1' ); }; var fn2 = function(arg){ console.log( arg + '2' ); cb.fire(); }; var fn3 = function(arg){ console.log( arg + '3' ); }; cb.add(fn1, fn2, fn3); cb.fire('fn'); // 其中回調fn1,fn2,fn3無限制的循環調用 /* 控制檯將無限制輸出以下: fn1 fn2 fn3 fn1 fn2 fn3 fn1 fn2 fn3 . . . */
實例二、 鎖定(lock)操做各類場景中的用法
var cb1 = jQuery.Callbacks(); var cb2 = jQuery.Callbacks('memory'); var cb3 = jQuery.Callbacks('memory'); var fn1 = function(arg){ console.log( arg + '1' ); }; var fn2 = function(arg){ console.log( arg + '2' ); }; var fn3 = function(arg){ console.log( arg + '3' ); }; // 若是options.memory !== true,鎖定操做視爲禁用回調對象 cb1.add(fn1); cb1.lock(); // 如下操做無任何反應 cb1.add(fn2); cb1.fire('fn'); // 若是fried !== true,鎖定操做也視爲禁用回調對象 cb2.add(fn1); cb2.lock(); // 如下操做無任何反應 cb2.add(fn2); cb2.fire('fn'); // 若是(fired === true && options.memory === true),鎖定操做相似控制標誌once(options.once === true); cb3.add(fn1); cb3.fire('fn'); // fn1,此時fired === true cb3.lock(); // 像是傳入了'once'標誌,jQuery.Callbacks('once memory'); cb3.add(fn2); // fn2 cb3.fire('fn'); // 再次觸發,無任何反應 cb3.add(fn3); // fn3 // 再來看看jQuery.Deferred中的一段源碼 var tuples = [ // action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ]; // Handle state if ( stateString ) { list.add(function() { // state = [ resolved | rejected ] state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); } /* 當執行了tuples中前面兩組中任意一個回調對象的fire方法時,後一組回調對象被鎖定, 至關於(fired === true && options.memory === true),後一組回調對象實際爲執行 jQuery.Callbacks('once memory')生成的回調對象。 */
PS: 若有描述錯誤,請幫忙指正,若是大家有不明白的地方也能夠發郵件給我,