那么就来读读jQuery源码的Callbacks部分。
一上来看原版源码
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; 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; };
如你所见,一开始声明了好几个变量,英文注释也说明变量干什么用,但事实上,你还是不知道这些个变量具体作用在哪。
那好,进入http://api.jquery.com/category/callbacks-object/
这里每个api说明,都有个demo,每个demo就相当于一个测试用例。
那么,依据测试用例,给源码设断点,一步步调试,看数据走向。但是一环嵌一环,绕来绕去,都记不住这些个变量变化的意义。
反正,一,句,话。我不知道作者为什么这么写,这个模块的设计思路是怎样。
怎么办,凉拌。
既然有了测试用例,那我就根据它来自己实现。如果实现的过程中,遇到困难,再看看源码,这样就更接近作者的思维,从而知道那些变量,判断的意义所在。
1.首先要实现的就是add和fire功能。
其思路是,引入一个函数管理数组变量,add就是把函数加进数组里,fire就是遍历数组,一个个调用函数。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>add和fire的功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { var arg = arguments; this.callbacks_list.forEach(function(fn) { fn.apply(this, arg); }) } } var foo = function(value) { console.log("foo: " + value); }; var bar = function(value) { console.log("bar: " + value); }; callbacks.add(foo); callbacks.fire("hello"); callbacks.add(bar); callbacks.fire("world"); </script> </head> <body> 现在的callback有add,fire功能 </body> </html>
2.加入remove功能。
思路是,遍历函数管理数组,判断其成员和传进来的参数是否一致,一致则移除。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>加入remove功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { var arg = arguments; this.callbacks_list.forEach(function(fn) { fn.apply(this, arg); }) }, remove: function(fn){ for(var i=0, l = this.callbacks_list.length; i < l; i++){ if(this.callbacks_list[i] == fn){ this.callbacks_list.splice(i,1); } } } } var foo = function(value) { console.log("foo: " + value); }; var bar = function(value) { console.log("bar: " + value); }; callbacks.add( foo ); callbacks.fire( "hello" ); callbacks.remove( foo ); callbacks.fire( "world" ); </script> </head> <body> 现在的callback有add,fire,remove功能 </body> </html>
3.加入disable功能。
disable在测试用例中所展示的意思就是,一旦disable了,再怎么fire,也不执行。
内部实现就是,把函数管理数组设置为undefined,然后在fire那边判断undefined,则跳出函数。
我这里忘了在add函数里,判断函数管理数组callbacks_list的undefined。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>3.加入disabled功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { if(this.callbacks_list == undefined || this.callbacks_list == null){ return; } var arg = arguments; this.callbacks_list.forEach(function(fn) { fn.apply(this, arg); }) }, remove: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { this.callbacks_list.splice(i, 1); } } }, disable: function() { this.callbacks_list = undefined; return this; }, disabled: function() { return !callbacks_list; } } var foo = function(value) { console.log(value); }; callbacks.add(foo); callbacks.fire("foo"); callbacks.disable(); callbacks.fire("foobar"); // foobar isn't output </script> </head> <body> 现在的callback有add,fire,remove,disable功能 </body> </html>
4.加入has功能
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>4.加入has功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { if (this.callbacks_list == undefined || this.callbacks_list == null) { return; } var arg = arguments; this.callbacks_list.forEach(function(fn) { fn.apply(this, arg); }) }, remove: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { this.callbacks_list.splice(i, 1); } } }, disable: function() { this.callbacks_list = undefined; return this; }, disabled: function() { return !callbacks_list; }, has: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { return true; } } return false; } } var foo = function(value1, value2) { console.log("Received: " + value1 + "," + value2); }; // A second function which will not be added to the list var bar = function(value1, value2) { console.log("foobar"); }; // Add the log method to the callbacks list callbacks.add(foo); // Determine which callbacks are in the list console.log(callbacks.has(foo)); // true console.log(callbacks.has(bar)); // false </script> </head> <body> 现在的callback有add,fire,remove,disable,has功能 </body> </html>
5.加入empty功能
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>5.加入empty功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { if (this.callbacks_list == undefined || this.callbacks_list == null) { return; } var arg = arguments; this.callbacks_list.forEach(function(fn) { fn.apply(this, arg); }) }, remove: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { this.callbacks_list.splice(i, 1); } } }, disable: function() { this.callbacks_list = undefined; return this; }, disabled: function() { return !callbacks_list; }, has: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { return true; } } return false; }, empty:function(){ this.callbacks_list = []; return this; } } // A sample logging function to be added to a callbacks list var foo = function(value1, value2) { console.log("foo: " + value1 + "," + value2); }; // Another function to also be added to the list var bar = function(value1, value2) { console.log("bar: " + value1 + "," + value2); }; // Add the two functions callbacks.add(foo); callbacks.add(bar); // Empty the callbacks list callbacks.empty(); // Check to ensure all callbacks have been removed console.log(callbacks.has(foo)); // false console.log(callbacks.has(bar)); // false </script> </head> <body> 现在的callback有add,fire,remove,disable,has,empty功能 </body> </html>
6.加入lock功能
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>6.加入lock功能</title> <script type="text/javascript"> var callbacks = { callbacks_list: [], memory: "", has_locked : false, add: function(fn) { this.callbacks_list.push(fn); }, fire: function() { if (this.callbacks_list == undefined || this.callbacks_list == null) { return; } else { if(this.has_locked == true){ var callback_this = this; var arg = callback_this.memory this.callbacks_list.forEach(function(fn) { fn.apply(callback_this, arg); }) } else{ var arg = arguments; var callback_this = this; this.memory = arg; this.callbacks_list.forEach(function(fn) { fn.apply(callback_this, arg); }) } } }, remove: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { this.callbacks_list.splice(i, 1); } } }, disable: function() { this.callbacks_list = undefined; return this; }, disabled: function() { return !callbacks_list; }, has: function(fn) { for (var i = 0, l = this.callbacks_list.length; i < l; i++) { if (this.callbacks_list[i] == fn) { return true; } } return false; }, empty: function() { this.callbacks_list = []; return this; }, lock: function() { this.callbacks_list = []; this.has_locked = true; } } /*demo1*/ var foo = function(value) { console.log("foo:" + value); }; var bar = function(value) { console.log("bar:" + value); }; callbacks.add(foo); callbacks.fire("hello"); callbacks.lock(); callbacks.fire("world"); /*demo2*/ // Add the foo function to the callback list again callbacks.add(foo); // Try firing the items again callbacks.fire("silentArgument"); // Outputs "foo: hello" because the argument value was stored in memory // Add the bar function to the callback list callbacks.add(bar); callbacks.fire("youHadMeAtHello"); </script> </head> <body> 现在的callback有add,fire,remove,disable,has,empty,lock功能 </body> </html>
还有未完成的就是,$.Callbacks()的参数once,memory,unique,stopOnFalse。
我一直思考的是,代码构建过程和纯逻辑思路,代码阅读。
一般来说,一个库的形成,是因为作者编写的过程中,发现一个个需求,或者一个个bug,然后需求和bug形成测试用例,根据测试用例,构造库。
它的库代码最后为什么会这个样子,一次次引入新变量,一个个判断来嵌套,还有为了库的使用便利,对函数参数做了各种判断处理,导致直接阅读有难度,不能通俗易懂。
那我们能否根据测试用例提取出这个模块的纯逻辑思路。至于逻辑思路的实现,交给开发自己代码能力实现。