jquery源码之低调的回调函数队列--Callbacks
jQuery中有一個很實用的函數隊列,可能我們很少用到,但他在jQuery內部卻有著舉足輕重的地位。
他就是Callbacks. jQuery作者用它構建了很多非常重要的模塊。比如說$.Deferred。
Callbacks 說白了就是個數組,里面存了很多函數對象。然而他真的 just so so么?
好吧,愛因斯坦也只是個人,但他真的僅僅是個普普通通的人嗎?Callbacks也不是。
不說廢話了,先看源碼。
?
// 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.split( core_rspace ), 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 firedfired,// Flag to know if list is currently firingfiring,// First callback to fire (used internally by add and fireWith)firingStart,// End of the loop when firingfiringLength,// Index of currently firing callback (modified by remove if needed)firingIndex,// Actual callback listlist = [],// Stack of fire calls for repeatable listsstack = !options.once && [],// Fire callbacksfire = 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 addbreak;}}firing = false;if ( list ) {if ( stack ) {if ( stack.length ) {fire( stack.shift() );}} else if ( memory ) {list = [];} else {self.disable();}}},// Actual Callbacks objectself = {// Add a callback or a collection of callbacks to the listadd: function() {if ( list ) {// First, we save the current lengthvar 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 recursivelyadd( 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 listremove: function() {if ( list ) {jQuery.each( arguments, function( _, arg ) {var index;while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {list.splice( index, 1 );// Handle firing indexesif ( firing ) {if ( index <= firingLength ) {firingLength--;}if ( index <= firingIndex ) {firingIndex--;}}}});}return this;},// Control if a given callback is in the listhas: function( fn ) {return jQuery.inArray( fn, list ) > -1;},// Remove all callbacks from the listempty: function() {list = [];return this;},// Have the list do nothing anymoredisable: function() {list = stack = memory = undefined;return this;},// Is it disabled?disabled: function() {return !list;},// Lock the list in its current statelock: function() {stack = undefined;if ( !memory ) {self.disable();}return this;},// Is it locked?locked: function() {return !stack;},// Call all callbacks with the given context and argumentsfireWith: function( context, args ) {args = args || [];args = [ context, args.slice ? args.slice() : args ];if ( list && ( !fired || stack ) ) {if ( firing ) {stack.push( args );} else {fire( args );}}return this;},// Call all the callbacks with the given argumentsfire: function() {self.fireWith( this, arguments );return this;},// To know if the callbacks have already been called at least oncefired: function() {return !!fired;}};return self; };?
?代碼只有僅僅200行不到,但真正看起來卻又點繞,
《think in java》中有這么一句,理解一個程序最好的方法,就是把它看做一個服務的提供者。
那他提供了那些服務:
首先我們看看返回的self對象
{// 添加方法add: function() {},// 刪除remove: function() {},// 是否包含has: function() {},// 清空empty: function() {},// 禁用disable: function() {},// 加鎖lock: function() {},// 是否加鎖locked: function() {},// 觸發fireWith: function(){},fire: function() {},// 是否觸發fired: function() {} }用途都十分清晰,那我們再看看參數,程序是服務的提供者,那么參數作為程序的入口的攜帶者,一般會用來裝配一些屬性。
顯然這里就是這樣。
?先看Callbacks內部關于參數部分的代碼。
// 官方注釋,將配置的options由string格式轉換為object格式如果需要的話// Convert options from String-formatted to Object-formatted if needed// (we check in cache first)options = typeof options === "string" ?// 注意這里, 這里去取optionsCache的值,或者調用( optionsCache[ options ] || createOptions( options ) ) :jQuery.extend( {}, options );在看看createOptions方法吧,其實就是個轉換方法,還帶有緩存功能。
// String to Object options format cache // 建立一個緩存對象 var optionsCache = {};// Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) {// 創建optionsCache中的options屬性var object = optionsCache[ options ] = {};// 這里用到 each方法遍歷// options.split( core_rspace ) 根據空格劃分為數組// _在jquery中通常用來作為占位符,即忽略的參數jQuery.each( options.split( core_rspace ), function( _, flag ) {// 遍歷以后將切割后的每個屬性設置為trueobject[ flag ] = true;});return object; } // 可能例子會更清晰, var obj = createOptions( "once memory"); /* obj; {once: true,memory: true } */接下來就是具體的實現了,jQuery的實現一直是十分巧妙的,當然這可能僅僅是小菜我看來。
/** 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的值,或者調用createOptions// 我們看看createOptions函數( optionsCache[ options ] || createOptions( options ) ) :jQuery.extend( {}, options );var // Last fire value (for non-forgettable lists)// 以前觸發的值(為了記憶的list,記憶了上次調用時所傳遞的基本信息(即記憶了參數))memory,// 是否觸發// Flag to know if list was already firedfired,// 是否正在觸發// Flag to know if list is currently firingfiring,// 第一個被觸發的function// First callback to fire (used internally by add and fireWith)firingStart,// 觸發列表的長度// End of the loop when firingfiringLength,// 當前觸發的索引// Index of currently firing callback (modified by remove if needed)firingIndex,// 內部存放function的數組// Actual callback listlist = [],// 用來存放重復調用的數組,(當Callbacks被配置了 once屬性,則為false)// Stack of fire calls for repeatable listsstack = !options.once && [],// 內部觸發函數,這里看到jquery隱藏信息的習慣了// 作為該模塊的核心方法// 它沒有暴露給外部,// 《代碼大全》 有提到信息隱藏的好處。// Fire callbacksfire = function( data ) {// 在設置memory的情況下為 傳遞過來的參數data, 否則為undefinedmemory = options.memory && data;// 進入到這時標記已觸發fired = true;// 當前觸發索引設置為開始,或者0firingIndex = firingStart || 0;firingStart = 0;firingLength = list.length;firing = true;// for循環觸發list中的函數for ( ; list && firingIndex < firingLength; firingIndex++ ) {// 如果stopOnFalse被設置,則檢查調用函數后是否返回false// 如果返回則終止觸發,// 注意觸發參數 為一個多維數組// data = [// context,// [args]//] 這應該是由外部封裝成固定格式,再傳遞過來的參數if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {memory = false; // To prevent further calls using addbreak;}}// 設置正在觸發為falsefiring = false;// 如果list是存在的,即改callbacks還沒有被禁用if ( list ) {// 如果 stack中有值,則遞歸調用// 其實這里是判斷是否設置了once屬性if ( stack ) {if ( stack.length ) {fire( stack.shift() );}} else if ( memory ) { // 如果設置記憶功能,則清空list(注意,是記憶需要調用的基本信息,即相關參數)list = [];} else {// 只能調用一次,且不能使用memory,// 則禁用self.disable();}}},// 再來看看需要暴露的對象// Actual Callbacks objectself = {// 添加方法// Add a callback or a collection of callbacks to the listadd: function() {// list其實是可以作為是否禁用的標志的,// 如果list存在if ( list ) {// First, we save the current lengthvar 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 recursivelyadd( arg );}});})( arguments );// Do we need to add the callbacks to the// current firing batch?// 如果正在觸發,則只需要更新firingLengthif ( firing ) {firingLength = list.length;// With memory, if we're not firing then// we should call right away// 如果memory,則在添加的時候直接觸發} else if ( memory ) {firingStart = start;fire( memory );}}return this;},// Remove a callback from the list// 刪除方法,遍歷刪除指定的方法,并維護好firingLength以及firingIndexremove: function() {if ( list ) {jQuery.each( arguments, function( _, arg ) {var index;while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {list.splice( index, 1 );// Handle firing indexesif ( firing ) {if ( index <= firingLength ) {firingLength--;}if ( index <= firingIndex ) {firingIndex--;}}}});}return this;},// Control if a given callback is in the list// 是否包含has: function( fn ) {return jQuery.inArray( fn, list ) > -1;},// Remove all callbacks from the listempty: function() {list = [];return this;},// Have the list do nothing anymoredisable: function() {list = stack = memory = undefined;return this;},// Is it disabled?disabled: function() {// 看,這里有用到list是否存在來判斷 是否被禁用return !list;},// Lock the list in its current state// 鎖住即不能再被觸發// 如果沒有設置memory則直接禁用lock: function() {stack = undefined;if ( !memory ) {self.disable();}return this;},// 是否加鎖// Is it locked?locked: function() {// 居然是判斷stack是否存在// 由此推斷 加鎖應該是設置智能觸發一次return !stack;},// Call all callbacks with the given context and argumentsfireWith: function( context, args ) {args = args || [];// 看這封裝了arguments,用來內部fire函數的調用args = [ context, args.slice ? args.slice() : args ];// 如果還沒被觸發,或者允許觸發多次if ( list && ( !fired || stack ) ) {// 正在觸發,則添加到stack// 在當次觸發后,直接觸發if ( firing ) {stack.push( args );} else {// 直接觸發fire( args );}}return this;},// Call all the callbacks with the given arguments// 設置context為thisfire: function() {self.fireWith( this, arguments );return this;},// To know if the callbacks have already been called at least oncefired: function() {return !!fired;}};// 注意有一個細節,self的所有方法都是返回的this// 這表明,它是支持鏈式操作的// jquery 很多地方用了這種優雅的技術return self; };好吧,Callbacks就講到這里了,神奇而低調的函數隊列,在以后的源碼中你也會經常看到他的身影,所以他能做什么并不用著急。
但還是舉些小例子用用看:
var c = $.Callbacks("once memory"); c.add(function(i) {alert(123 + '-' + i); }); c.add(function(i) {alert(234 + '-' + i); }); c.add(function(i) {alert(456 + '-' + i); }); c.fire('tianxia'); // alert('123-tianxi'); alert('234-tianxi'); alert('456-tianxi'); c.fire(); // 再次調用,啥都沒發生,因為設置了once // 什么都沒發生 c.add(function(i) {alert(i); }); // alert('tianxia') // 在設置memory,添加后,直接觸發
轉載于:https://www.cnblogs.com/w2154/p/4570599.html
總結
以上是生活随笔為你收集整理的jquery源码之低调的回调函数队列--Callbacks的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哪里可以看到AiMesh 路由器与节点间
- 下一篇: 腾达无线路由器adsl拨号上网设置指南