jQuery.Callbacks源码解读
序言:
最近在学习jQuery.Callbacks对象,看jQuery.Callbacks的API文档,不是很懂,因此看看其源码部分,理解其使用方法,记录下自己在阅读源码时的记录并分享给大家。
有理解不透的地方望同仁指点,代码来源:jQuery 1.9.1版本。作者:华子yjh,欢迎转载,转载时请注明出处并附上原文链接。
一、源码解读
/* * 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: 确保回调列表仅只fire一次 will ensure the callback list can only be fired once (like a Deferred) * * memory: 在执行过fire后,保存之前fire时的参数,该参数会传递给在add中执行的最新添加的回调 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: 确保在add操作中,阻止存在回调列表中的回调再次被添加到回调列表中 will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: 当正在执行的回调返回false,将中断其他未执行回调的执行 interrupt callings when a callback returns false * */ var optionsCache = {}, // Used for splitting on whitespace core_rnotwhite = /\S+/g; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { // optionsCache[ options ] 用于缓存 object所引用的值 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" ? // 只有当执行$.Callbacks(参数相同)二次及以上时,才不会执行createOptions函数 ( optionsCache[ options ] || createOptions( options ) ) : // 说明也可以这样$.Callbacks({once:true, memory:true})使用 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 // data传递的是一个数组 // 使用Callbacks.fireWidth时,data包含fireWith函数传递的一个上下文环境和一个数组 // 使用Callbacks.fire时,data包含Callbacks对象和fire函数的arguments对象 fire = function( data ) { // 如果options.memory为true,记录下data传递的数组 memory = options.memory && data; fired = true; // 如果options.memory为true,firingStart为上一次Callbacks.add后回调列表的length值 firingIndex = firingStart || 0; // 重置firingStart为0 firingStart = 0; firingLength = list.length; firing = true; for ( ; list && firingIndex < firingLength; firingIndex++ ) { // 将data作为参数,执行回调列表中的所有回调 // 如果回调列表中其中一个回调返回false,且options.stopOnFalse为true,则中断接下来其他回调的执行 // 如果options.memory为true,将memory设置为false,阻止在Callbacks.add中新增回调的执行 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { memory = false; // To prevent further calls using add break; } } firing = false; if ( list ) { // 如果options.once为false if ( stack ) { // stack在fireWith操作时可能拥有成员 // 当stack拥有成员时(只有一个),并将其成员作为参数传递给将要执行的所有回调 if ( stack.length ) { fire( stack.shift() ); } } // 如果options.memory为true且options.once为true,那么在add中执行一次fire(只执行最新添加的回调) // 这里设置list = [],那么再次fire时,将不会有任何操作 else if ( memory ) { list = []; } // 如果存在options.once为true,options.memory为false的其他情况,将禁用Callbacks对象 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 ); // 如果arg是一个函数 if ( type === "function" ) { // arg为要新增到回调列表中的回调 // 如果arg不存在回调列表中,则将它增加到回调列表中 // 如果arg存在回调列表中,且options.unique为false,则将它增加到回调列表中 // 如果arg存在回调列表中,且options.unique为true,则不执行push动作 if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } // 如果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? // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作 // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时 // 那么需要更新firingLength值 if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away // 如果options.memory为true,则将memory做为参数,应用最近增加的回调函数 } 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; // 通过找到arguments成员在回调列表中索引位置遍历arguments对象,并将arguments成员从回调列表中移除 while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes // 如果在执行Callbacks.remove操作的状态为firing时 // 则更新firingLength和firingIndex的值 if ( firing ) { if ( index <= firingLength ) { firingLength--; } // 特殊处理,如果移除的回调的索引小于当前正在执行回调的索引,则firingIdex-- // 后面未执行的回调则得以正常执行 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. // 检查fn是否存在回调列表中 has: function( fn ) { return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); }, // Remove all callbacks from the list // 清空回调列表中的所有回调 empty: function() { list = []; return this; }, // Have the list do nothing anymore // 禁用Callbacks对象 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 ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; if ( list && ( !fired || stack ) ) { // 如果fired为true,options.once为false,且如果在执行fireWith操作的状态为firing // 则将处理过的args作为stack数组的第一个数组项,并在firing为false后,将stack第一个数组项作为参数传递给将要执行的所有回调 // 如果fired为true,options.once为true,则不执行任何操作 // 可以看出,$.Callbacks('once')表示回调对象只fire一次 if ( firing ) { stack.push( args ); } // 第一次执行fireWith时,一定执行else分支 else { fire( args ); } } return this; }, // Call all the callbacks with the given arguments // 将Callbacks对象,及Callbacks.fire函数中的arguments对象传递给Callbacks.fireWith函数,并执行 fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once // 是否执行过fire函数 fired: function() { return !!fired; } }; //返回Callbacks对象 return self; };
二、jQuery.Callbacks内部中firing为true的实例
1、在Callbacks.add中firing为true的实例
$(function(){ // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3 function fn1(arg){ console.log( 'fn1 says:' + arg ); // 在fn1中执行Callbacks.add操作,此时Callbacks函数内部的firingLength将会得到更新 $callbacks.add(fn2); } function fn2(arg){ console.log( 'fn2 says:' + arg ); } function fn3(arg){ console.log( 'fn3 says:' + arg ); } // Callbacks传递了memory // 也可以这样使用$.Callbacks({ memory: true }); var $callbacks = $.Callbacks('memory'); // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2 $callbacks.add(fn1); // output: fn1 says:foo // output: fn2 says:foo $callbacks.fire('foo'); // 将之前fire的参数传递给最近增加的回调fn3,并执行fn3 // output: fn3 says:foo $callbacks.add(fn3); // 再执行一次fire,注意此时回调列表中的回调一次是fn1,fn2,fn3,fn2 // output: fn1 says:baz // output: fn2 says:baz // output: fn3 says:baz // output: fn2 says:baz // 如果期望回调列表中只有fn1,fn2,fn3,只需在Callbacks函数中传入unique $callbacks.fire('baz'); });
2、在Callbacks.fireWith中firing为true的实例
$(function(){ function fn1(arg){ console.log( 'fn1 says:' + arg ); } function fn2(arg){ console.log( 'fn2 says:' + arg ); $callbacks.fireWith(window, ['yjh']); // 一定要执行这一步,否则将会陷入死循环 $callbacks.remove(fn2); } var $callbacks = $.Callbacks(); $callbacks.add(fn1); // output: fn1 says:foo $callbacks.fire('foo'); $callbacks.add(fn2); // output: fn1 says:baz // output: fn2 says:baz // output: fn1 says:yjh $callbacks.fire('baz'); });