测试代码1:
//callbacks里面添加了两次f函数,但是fire()时候只会调用一次,也就是由于两次添加的函数相同,那么会检测到重复的 //调用。同时jQuery.Callbacks1("unique")调用时候会把options["unique"]生成true!于是!options["unique"]为false //那么就会判断!self.has( arg ),然而has方法的底层用的是jQuery.inArray( fn, list ),所以如果有重复的就不会被添加进去! //但是,如果把上面jQuery.Callbacks1("unique")的"unique"字符串去掉,那么就会调用两次,因为会把两次相同的函数都添加进去 //因为本身options["unique"]为false callbacks.add(f) callbacks.add(f) callbacks.fire(); //输出结果: "a"测试代码2(说明观察者模式:)
var Observable = { callbacks: [], add: function(fn) { this.callbacks.push(fn); }, fire: function() { this.callbacks.forEach(function(fn) { fn(); }) } } Observable.add(function() { alert(1) }) Observable.add(function() { alert(2) }) Observable.fire(); // 1, 2测试代码3(什么是重复函数:)
var f=function(){alert("xxx");} var g=function(){alert("yyy");} var arr=[f,g] //打印"0->1" alert(jQuery.inArray(f,arr)+"->"+jQuery.inArray(g,arr)); //打印-1,所以只有通过函数句柄传递才会有函数地址相同进而是同一个函数之称,向下面这样虽然函数定义相同但是地址是不同的 //这是为什么下面会弹出"a,a" var arr1=[function(){alert("xxx");},function(){alert("yyy");}] alert(jQuery.inArray(function(){alert("yyy");},arr1)); //这里为什么会调用两次原因如上: var callbacks = jQuery.Callbacks("unique"); var f=function() { alert('a'); }; var g=function(){alert("a");} callbacks.add(f); callbacks.add(g); callbacks.fire();
测试代码4(once有什么用:破坏性极大,调用一次以后让list=memory=stack=undefined)
function fn1( value ) { alert( value ); } function fn2( value ) { fn1("fn2 says: " + value); return false; } var callbacks = $.Callbacks( "once" );//once表示只会执行一次! callbacks.add( fn1 ); callbacks.fire( "foo" ); //这时候stack是false(stack=!option.once&&[]),memory不存在(没有传入参数memory),调用self.disable,memory+list+stack都是undefined了! callbacks.add( fn2 ); callbacks.fire( "bar" ); //上面的继续添加和继续调用没有任何意义,因为list,stack,memory都是undefined根本就不会被存储进入list集合!所以后面四句代码都是没有意义的! callbacks.remove( fn2 ); callbacks.fire( "foobar" );
测试代码5(memory有什么用:保存上一次的参数,因为memory是全局变量,在add中会自己用旧参数调用新函数)
(1)案例1
function fn1( value ) { alert("fn1 says:"+ value ); } function fn3( value ) { alert("fn3 says:"+ value ); } function fn2( value ) { alert( "fn2 says: " + value ); return false; } //我们必须弄清楚:memory是全局变量,每次fire调用的时候都会用memory记住本次调用的参数!除非下次fire调用时候用参数把它覆盖掉! var callbacks = $.Callbacks( "memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); //在add中有一个关于memory情况的判断,else if ( memory )也就是说如果有memory那么就会执行这里的逻辑,结果就是用上一次的参数把所有添加的参数执行一遍! //这里新添加就是fn1,fn2,所以会用上一次的参数foo执行两个函数!这其实说白了就是memory的作用:记住上一次的参数调用后面添加的函数而不用手动调用! callbacks.add( fn2 ); callbacks.add( fn3 );(2)案例2(上面没有手动调用fn2,fn3而是因为在add中如果是memory就会拿着旧的参数调用新的函数,是自动调用,我们这里手动调用一下)
function fn1( value ) { alert("fn1 says:"+ value ); } function fn3( value ) { alert("fn3 says:"+ value ); } function fn2( value ) { alert( "fn2 says: " + value ); return false; } //我们必须弄清楚:memory是全局变量,每次fire调用的时候都会用memory记住本次调用的参数!除非下次fire调用时候用参数把它覆盖掉! var callbacks = $.Callbacks( "memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); //在add中有一个关于memory情况的判断,else if ( memory )也就是说如果有memory那么就会执行这里的逻辑,结果就是用上一次的参数把所有添加的参数执行一遍! //这里新添加就是fn1,fn2,所以会用上一次的参数foo执行两个函数!这其实说白了就是memory的作用:记住上一次的参数调用后面添加的函数而不用手动调用! callbacks.add( fn2 ); callbacks.add( fn3 ); callbacks.fire();
note:这种情况不再依赖于add方法在memory情况下的自动调用了,而是手动调用,但是一定要知道,add方法已经调用过了!也就是在你没有调用fire的时候已经被调用过了!那么我这里的fire有什么作用呢?其实就是把list里面的函数,fn1,fn2,fn3用自己提供的参数调用一次罢了!打印[foo,foo,foo,undefined,undefined,undefined]
(3)案例3(不依赖于add方法的调用,我们自己提供参数来调用,但是 记住add在你没有手动调用fire的时候已经调用过了!)function fn1( value ) { alert("fn1 says:"+ value ); } function fn3( value ) { alert("fn3 says:"+ value ); } function fn2( value ) { alert( "fn2 says: " + value ); return false; } //我们必须弄清楚:memory是全局变量,每次fire调用的时候都会用memory记住本次调用的参数!除非下次fire调用时候用参数把它覆盖掉! var callbacks = $.Callbacks( "memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" ); //在add中有一个关于memory情况的判断,else if ( memory )也就是说如果有memory那么就会执行这里的逻辑,结果就是用上一次的参数把所有添加的参数执行一遍! //这里新添加就是fn1,fn2,所以会用上一次的参数foo执行两个函数!这其实说白了就是memory的作用:记住上一次的参数调用后面添加的函数而不用手动调用! callbacks.add( fn2 ); callbacks.add( fn3 ); callbacks.fire("xxx");note:打印[foo,foo,foo,xxx,xxx,xxx]原理和上面的例子是一样的!
var f1 = function() { alert("f1"); }; var callbacks = $.Callbacks(); callbacks.add(f1); callbacks.add(f1); callbacks.fire(); //输出 f1 f1 //传递参数 "unique" callbacks = $.Callbacks("unique"); callbacks.add(f1); //有效 callbacks.add(f1); //添加不进去 callbacks.fire(); //输出: f1
测试代码7(stopOnFalse有什么用:如果前面的函数返回false后面函数停止执行,同时memory也失效)
代码1:
function fn1( value ) { alert( value ); return false; } function fn2( value ) { fn1( "fn2 says: " + value ); return false; } var callbacks = $.Callbacks( "stopOnFalse" ); callbacks.add( fn1 ); //这时候列表只有一个fn1所以打印foo callbacks.fire( "foo" ); //这时候列表有fn1,fn2但是因为调用fn1时候return false所以后面的fn2不会调用,打印[bar] callbacks.add( fn2 ); callbacks.fire( "bar" ); //移除fn2以后列表剩下fn1,继续调用打印[foobar]. //总之:如果有stopOnFalse,那么只要当前callback列表里面有一个返回了false,那么列表里面 //其它的回调函数都不会执行! callbacks.remove( fn2 ); callbacks.fire( "foobar" );代码2:
function fn1( value ) { alert( value ); return false; } function fn2( value ) { fn1( "fn2 says: " + value ); } var callbacks = $.Callbacks( "stopOnFalse memory" ); callbacks.add( fn1 ); //这时候列表只有一个fn1所以打印foo callbacks.fire( "foo" ); //这里没有被调用,因为上面fn1已经return false那么memory已经失效,所以后面添加的函数必须手动调用,add方法里面检测到memory已经为false //不会自动执行!那么如果你自己在后面添加代码callbacks.fire("xxx");会怎么?结果就是只是将fn1再次调用一次罢了,同时break也就是说如果有一个函数 //return false那么后面的函数都不会执行了! callbacks.add(fn2);
测试代码8:(unique和memory组合)
function fn1( value ) { alert( value ); return false; } function fn2( value ) { fn1( "fn2 says: " + value ); return false; } var callbacks = $.Callbacks( "unique memory" ); callbacks.add( fn1 ); callbacks.fire( "foo" );//打印[foo] callbacks.add( fn1 ); // 重复添加不进去 callbacks.add( fn2 );//添加了fn2,回调列表为fn1,fn2 callbacks.fire( "bar" );//上次的foo已经memory下来,新添加的函数fn2第一步就是用memory的参数调用得[fn2 says foo],第二步用参数bar都调用一遍[bar,fn2 says bar] callbacks.add( fn2 ); callbacks.fire( "baz" );//重复添加memory上一次的参数是bar,重复添加没有新函数,省略第一步,即省略用老参数调用新函数,直接第二步都执行一遍[baz,fn2 says baz] callbacks.remove( fn2 ); callbacks.fire( "foobar" );//移除fn2只有fn1,打印[foobar]测试代码9:(firing的作用防止死循环,作用于fire函数,如果是只是调用一次once那么就不会添加)
function aa() { alert(1); cb.fire(); } function bb() { alert(2); } var cb=$.Callbacks(); cb.add(aa); cb.add(bb); cb.fire(); // cb.fire();//这里写一句代码那么肯定会再次执行aa,bb函数,那么如果把这一句代码放在aa结束以后呢? //那么上面就会出现死循环!那么这时候就会一直执行aa,bb因为死循环了!note:当下面调用fire的时候list里面有aa,bb函数,所以直接调用aa,bb函数,但是在aa里面继续调用aa,bb函数这时候源码里面的firing还是true,因为上面的list集合没有循环结束!在源码里面的做法就是直接把这后面的再次调用放在调用队列里面stack(保存参数就可以),当前面那一次调用完了就再次调用后面fire函数并传入新的参数!
var flag=true; function aa() { alert(1); if(flag) { cb.fire();//这里的触发不会立即执行这里,先会执行bb,这相当于把这一次触发放在运行过程的队列当中! flag=false; } } function bb() { alert(2); } var cb=$.Callbacks(); cb.add(aa); cb.add(bb); cb.fire(); //打印1,2,1,2测试代码10:(为什么在fire后面除了判断stack以外还要判断memory)
function aa() { alert(1); } function bb() { alert(2); } var cb=$.Callbacks("once"); cb.add(aa); cb.fire();//如果callback只有once,那么fire只会执行一次!所以后面的fire不会执行!因为没有memory所以走else执行disbaled cb.add(bb); cb.fire();note:这个例子告诉我们因为这里是once,所以stack就是false,同时也没有memory,那么直接调用self.disable,清空了stack,list,memory那么后面的调用相当于不存在!因为list已经不存在了,所以再次添加也是不能被调用的!
function aa { alert(1); } function bb() { alert(2); } var cb=$.Callbacks("once memory");//这时候有memory! cb.add(aa); cb.fire(); cb.add(bb); cb.fire();//因为有memory那么就把list清空,所以这里的fire调用只是执行了空数组了!相当于没有弹出!note:这就是once和memory同时存在的情况,在源码里面走了else if的逻辑,结果就是把list=[]。这时候因为有once所以stack长度为0,但是因为有memory所以最后把list变成了空数组,也就是调用过后直接把数组变成了空!这时候如果你继续add,然后再次fire还是可以执行的!
总之:
(1)once作用更加强烈直接把list,stack,memory变成了undefined(那么为什么要把memory也变成undefined?因为在add方法里面会对memory进行判断,如果memory存在,那么直接调用fire(memory)其中memory= options.memory && data,也就是用旧的参数调用新的函数)。但是在once这种情况下list已经是undefined了,所以哪怕你调用fire之后继续添加函数也不会被调用,因为这时候的list已经不存在了,拿着参数是没有意义的!但是对于once+memory的组合是有意义的!
(2)once+memory的情况更加柔和一点只是把list=[]!所以当后面你继续添加的函数的时候,list里面又有了新的函数,所以当你继续调用fire(可以传入参数)的时候因为list又有了函数所以还是会执行的!但是以前的函数已经被清空了!
(3)对于once+memory的组合还有一个特点,说白了其实是memory的特点。也就是当你在once+memory的情况下,调用了一次fire以后(list已经是[]了),当你继续添加函数这时候可以不手动调用fire函数,因为在add方法里面进行了判断,如果有memory那么就会自动拿着参数去调用list里面所有的方法!(如果没有参数就是undefined!)
(4)在没有once的情况下,stack肯定存在!这时候调用fire不会对list,stack,memory等产生任何影响,除非你手动调用disable,empty等函数!也就是once调用了disable,once+memory的组合调用了list=[]!其它fire调用不会影响内部的机制(如memory等stack都会存在)!
测试代码9:
var callbacks = $.Callbacks(), add = callbacks.add, remove = callbacks.remove, fire = callbacks.fire; add( fn1 ); fire( "hello world" );//[hello world] remove( fn1 );
测试代码10:
var f1 = function() { alert("f1"); return false }; //注意 return false; var f2 = function() { alert("f2"); }; var f3 = function() { alert("NB"); }; callbacks = $.Callbacks("memory stopOnFalse"); callbacks.add(f3); callbacks.add(f1); callbacks.add(f2); callbacks.fire(); //输出[NB,f1]因为f1的return false所以f2没有输出 callbacks.add(function() { alert("f3"); }); //不会输出,memory已经失去作用了 callbacks.fire(); //因为这里有memory所以会记住上一次调用的参数,但是因为有stopOnFalse所以memory失效,也就是不会用老参数去调用新函数了。所以直接第二步,给列表[f3,f1,f2,匿名函数]都执行一遍,但是因为有stopOnFalse所以到f1结束了,打印[NB,f1]<span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
测试代码11:(观察者订阅者模式:点击打开链接)
function fn1( value ) { alert( value ); } function fn2( value ) { alert( "fn2 says: " + value ); return false; } var topics = {}; jQuery.Topic = function( id ) { var callbacks, method, topic = id && topics[ id ]; //如果不存在topics[id] if ( !topic ) { //获取Query.Callbacks() callbacks = jQuery.Callbacks(); //给topic重新赋值具有publish,subscribe,unsubscribe方法! topic = { publish: callbacks.fire, subscribe: callbacks.add, unsubscribe: callbacks.remove }; //传入了id,给topics[id]赋值为新对象 if ( id ) { topics[ id ] = topic; } } return topic; }; $.Topic( "mailArrived" ).subscribe( fn1 ); //第二次mailArrived直接返回topic,因为topic = id && topics[ id ]非空! $.Topic( "mailArrived" ).subscribe( fn2 ); //添加事件mailSent $.Topic( "mailSent" ).subscribe( fn1 ); // Publisher $.Topic( "mailArrived" ).publish( "hello world!" ); $.Topic( "mailSent" ).publish( "woo! mail!" ); $.Topic( "mailArrived" ).subscribe( fn1 ); //创建Deferred对象 var dfd = $.Deferred(); //新事件 var topic = $.Topic( "mailArrived" ); //当Deferred已经resolve了,通知订阅者 dfd.done( topic.publish ); //这里Deferred已经resolve了,并且传入了信息给消息订阅者 //这种方式可以扩展到更加复杂的情形下,例如等待一个ajax请求完成 //以便消息只会给订阅者传送一次! dfd.resolve( "it's been published!" );
总结:disabled调用之后,即使后面再添加回调事件,然后fire那么也不会调用;empty调用之后会把list集合清空,这时候has检测不到任何函数;lock方法后stack是undefined,如果没有memory那么直接调用disable,否则在fire调用的代码中会导致stack为fasle从而走memory的逻辑,也就是list=[]所以相当于星空了list集合!(在lock中检测了是否有memory如果没有memory那么直接调用disable)点击打开链接
下面是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; };
下面是里面调用的函数:
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; }补充:关于lock时候所走的代码逻辑
lock: function() { //首先把stack设置为undefined stack = undefined; if ( !memory ) { //如果没有memory,那么好办,结果就是调用disable方法,让stack=memory=list=null //不能再次进行添加,然后fire操作 self.disable(); } return this; }那么凭什么说就是把list的状态锁住了呢,如果有memory的情况又是怎么回事呢?我们继续分析fireWith中的代码:
fireWith: function( context, args ) { //如果有memory那么list还是存在的,但是fired已经为true(lock之前被调用至少一次) //同时在lock里面把stack设置为undefined,结果就是调用fire或者fireWith没有任何作用 //因为fire也是调用的fireWith方法 if ( list && ( !fired || stack ) ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; if ( firing ) { stack.push( args ); } else { fire( args ); } } return this; }总结:从fireWith代码中可以看到,如果调用了lock以后后面所有的fire操作全部被忽略,也就是什么都不执行!