引入
初看Callbacks函數很不起眼,但仔細一瞅,發現Callbacks函數是構建jQuery大廈的無比重要的一個基石。jQuery中幾乎所有有關異步的操作都會用到Callbacks函數。
為什么搞了個Callbacks函數?
1 在 js 開發中,經常會遇到同步和異步這兩個概念。
2 在javascript中神馬是同步?神馬是異步? 聽我講一個相親的故事(本故事並不准確,僅供參考):
1 藤籃是一個漂亮姑娘,明年就要30歲了可現在還沒有對象,於是,她的母親給她報名了兩家相親機構,一家名叫同步相親機構,另一家叫異步相親機構。
2 同步相親機構:這個機構的負責人很呆板,嚴格遵從“先來后到”的理念。
負責人首先給藤籃一個小冊子,里面記錄了很多的男士資料,讓藤籃從里面找一個心儀的男士,然后安排藤籃與他見面。
藤蘭很快選中了令狐沖。負責人告訴藤籃:“令狐沖明天和任女士有約,你們只能后天見面了“。藤籃說:“好的,我正好准備准備”。
結果兩天過后,負責人告訴藤籃:因為昨天任女士有事,沒能和令狐沖見面,所以我們安排今天任女士與令狐沖見面,你到明天再約吧!
藤籃很生氣:既然昨天任女士有事,為啥不安排讓我昨天和令狐沖見面呢?
負責人說:不行!俺們講究的是先來后到! 因為任女士先約的令狐沖,甭管怎么着,你都得排在任女士后面
藤籃很生氣! 於是來到了異步相親機構。
3 異步相親機構:這個機構的負責人則很靈活:
一般情況下遵從先來后到的理念,特殊情況特殊對待。
藤籃很喜歡這個負責人的理念,於是和這里的負責人攜手一生.......
4 再來總結一下:Javascript語言的執行環境是"單線程",所謂"單線程",就是指一次只能完成一件任務。
同步就是: 后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的
異步就是:一個任務可能有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而執行回調函數,
后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的
3 上面異步中講到,一個任務可能有一個或多個回調函數,假如a任務有a1,a2,a3這三個回調函數,b也需要a1,a2,a3這三個回調函數。我們需要這樣做:
function a1(){ } function a2(){ } function a3(){ } var a = setTimeout(function(){ a1(); a2(); a3(); },1000); var b = setTimeout(function(){ a1(); a2(); a3(); },2000)
4 上面的代碼很麻煩是不是?Callback就是來解決這種麻煩!!
Callbacks如何解決這種麻煩?
先來說一下大體思路:
1 首先我們每次調用Callbacks(),都會返回一個callbacks對象,這個對象有一個僅僅只有自己才能訪問的數組(就是用了閉包唄)
2 這個數組就是用來存儲 回調函數的。
3 callbacks對象有許多方法,可以對數組進行操作,比如說:添加(add),清空(empty),刪除(remove),運行數組中的所有回調函數(fire).......
4 那么,我們上面---為什么搞了個Callbacks函數?---的第三條 任務a,任務b可以如下改寫:
function a1(){ } function a2(){ } function a3(){ } var allCallback = $.Callbacks(); allCallback.add(a1,a2,a3); var a = setTimeout(function(){ allCallback.fire() },1000); var b = setTimeout(function(){ allCallback.fire() },2000)
升華:
Callbacks可不僅僅只實現了這些,他還提供了很多參數: var a = Callbacks('once memory unique stopOnFalse ')
once: 如果創建Callbacks時加入該參數,則運行數組中的所有回調函數之后,也就是fire()之后,會清空數組。
memory: 會保存上一次運行fire(args)時的參數args,每當添加一個新的回調函數到數組中,會立即使用args作為參數調用新加的函數一次
unique: 確保數組中的回調函數互不相同
stopOnFalse: 當運行fire()時,若某個回調函數返回false,則立即終止余下的回調函數執行
盡管這些參數可能有些初看沒啥用。 但是不得要說,jQuery的開發人員真的很細致!
源碼講解:
define([ "./core", "./var/rnotwhite" ], function( jQuery, rnotwhite ) { // String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache /* 如果: var a = $.Callback('once memory') 則 optionsCache中會有這么一項:"once memory":{memory:true,once:true} */ function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( 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 list是否已經被fire函數調用過 fired, // Flag to know if list is currently firing 當前是否正在調用fire函數 firing, // First callback to fire (used internally by add and fireWith) 第一個被執行的回調函數在list的位置 firingStart, // End of the loop when firing fire函數要運行的回調函數的個數 firingLength, // Index of currently firing callback (modified by remove if needed) 當前正在執行的回調函數的索引 firingIndex, //回調函數數組 list = [], // Stack of fire calls for repeatable lists 可重復的回調函數棧。我們可能會短時間內執行多次fire(),若當前的fire()正在迭代執行回調函數,而緊接着又執行了一次fire()時,會將下一次的fire()參數等保存至stack中,等待當前的fire()執行完成后,將stack中的fire()進行執行 stack = !options.once && [], // Fire callbacks fire = function( data ) { // data[0] 是一個對象,data[1]則是回調函數的參數 memory = options.memory && data; // 很精妙,仔細體會一下這句代碼,如果調用Calbacks時傳入了memory,則memory = data,否則memory = false fired = true; // 在調用本函數時,將fired狀態進行修改 firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; firing = true; // 迭代回調函數之前,將firing狀態進行修改 for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { // 運行回調函數的同時,檢測回調函數是否返回false,若返回false,且調用Callbacks時傳入stopOnFalse參數,則終止迭代 memory = false; // To prevent further calls using add 既然終止迭代了,那么之后添加的回調函數都不應該被調用,將memory設置為false break; } } firing = false; // 迭代回調函數完成后,將firing狀態進行修改 if ( list ) { if ( stack ) { // 沒有使用once參數 if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { // 使用了once memory參數,則在迭代完回調函數之后清空list list = []; } else { // 其他 self.disable(); } } }, // Actual Callbacks object self = { // 將一個新的回調函數添加至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 ); // 若參數中的元素為函數且(無unique參數或者list中沒有該函數),則將該函數添加至list末尾 } } else if ( arg && arg.length && type !== "string" ) { // arg的長度不為0且每項的類型不為字符串,也就是args為這種情況:[[fun1,fun2...],[fun3,fun4]](不僅限於這種情況) // Inspect recursively add( arg ); } }); })( arguments ); // Do we need to add the callbacks to the // current firing batch? // 當Callback中的firingLength變為 動態的! 也就是:只要我們向list中添加了一個新的回調函數,即使在fire()運行過程中,改變也能立即體現出來 if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away } else if ( memory ) { // 如果當前沒有執行回調函數,且存在memory參數,則執行新添加的回調函數 firingStart = start; fire( memory ); } } return this; }, // Remove a callback from the list 將一個回調函數從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 // 使用傳入的context作為當前函數的執行上下文 fireWith: function( context, args ) { if ( list && ( !fired || stack ) ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; if ( firing ) { stack.push( args ); // 如果當前正在迭代執行回調函數,則將新的fire參數推入stack中 } 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 // 用來確定當前callback對象是否被fire()過 fired: function() { return !!fired; } }; return self; }; return jQuery; });
