?????????? 声明:写博客,是对自身知识的一种总结,也是一种分享,但由于作者本人水平有限,难免会有一些地方说得不对,还请大家友善? 指出,或致电:tianzhen.chen0509@gmail.com
???????????关注:国内开源jQuery-UI组件库:Operamasks-UI
?????? jQuery版本:v1.7.1
???????????????? jQuery1.7系列三:? jQuery延迟列表(Deferred)
一.函数列表
?
?????? 很多时候,我们想要执行一系列的函数,比如,当一个ajax请求后想要执行2个成功函数;当一个动画结束后想要执行2个函数,那么我们很容易想到用一个数组来承载这些待执行函数,如下所示:
var callbacks = []; callbacks.push(function(){}); callbacks.push(function(){});
????? callbacks这个函数列表除了装载函数外,没有任何多余的控制能力,在很多时候我们需要更加智能的函数列表。比如,它可以控制不可以添加重复的函数;可以控制何时开始触发这些函数;可以控制当某个函数返回了false时后边的函数将不再执行……
?????? 如果拥有了这样强大的函数列表,那么很多不可思议的功能将可以轻松的实现,而这一切都可以在jQuery中找到踪迹。
?
二.jQuery加强的函数回调列表(Callbacks)
?
??????? jQuery1.7提供了一个加强的函数回调列表,它不仅仅用于存放函数,最重要的是它提供了更加复杂的控制,如何时才开始执行这些函数,这些函数是否可以重复。大体情况可以看下图:
jQuery.Callbacks便是它的实现,比如,看个最简单的代码:
<script> $(function($){ var callbacks = $.Callbacks(); callbacks.add(function(text){console.log("f1"+text);}); callbacks.add(function(text){console.log("f2"+text);}); //调用fire/fireWith才会执行这些函数,并且可以传递参数 callbacks.fire("test");//输出结果: f1test f2test }); </script>
?
接下来,我们分别看下四个标准的控制标志。
1.????once
见名知义,只可以触发一次函数列表,也就是只有第一次调用fire/fireWith才会生效。
<script> $(function($){ //注意传递了参数 "once" var callbacks = $.Callbacks("once"); callbacks.add(function(){console.log("f1");}); callbacks.fire();//输出 "f1" callbacks.fire();//什么也不发生 callbacks.add(function(){console.log("f2");}); callbacks.fire();//还是什么也不发生 //默认为非"once" var callbacks = $.Callbacks(); callbacks.add(function(){console.log("f3");}); callbacks.fire();//输出f3 callbacks.fire();//输出f3 }); </script>
2.????memory
单看名字我觉得很难知道是什么意思,它的作用是在add一个函数的时候,如果这时候函数列表已经全部执行完了,那么刚刚add进去的函数会立即执行。
<script> $(function($){ //注意传递了参数 "once" var callbacks = $.Callbacks("memory"); callbacks.add(function(){console.log("f1");}); callbacks.fire();//输出 "f1",这时函数列表已经执行完毕! callbacks.add(function(){console.log("f2");});//memory作用在这里,没有fire,一样有结果: f2 callbacks.fire();//再触发一次,输出 f1 f2 //与once一起使用 callbacks = $.Callbacks("once memory"); callbacks.add(function(){console.log("f3");}); callbacks.fire();//输出 "f3",这时函数列表已经执行完毕! callbacks.add(function(){console.log("f4");});//没有fire,一样有结果: f4 callbacks.fire();//由于为"once",这里将什么也不执行 }); </script>
3.????unique
函数列表中的函数是否可以重复,这个也很简单。
?
<script> $(function($){ var f1 = function(){console.log("f1");}; var callbacks = $.Callbacks(); callbacks.add(f1); callbacks.add(f1); callbacks.add(f1); callbacks.fire();//输出 f1 f1 f1 //传递参数 "unique" callbacks = $.Callbacks("unique"); callbacks.add(f1); //有效 callbacks.add(f1); //添加不进去 callbacks.add(f1); //添加不进去 callbacks.fire();//输出: f1 }); </script>
?
4.?????stopOnFalse
?????? 默认情况下,当fire函数列表的时候,整个列表里边所有函数都会一一执行,但如果设置了stopOnFalse,那么当某个函数返回了false时,后 边的函数将不再执行。即使设置了memory,再次添加的函数也不会执行了。但如果没有设置”once”,再次调用fire还是可以再次重新触发的。
<script> $(function($){ var f1 = function(){console.log("f1"); return false};//注意 return false; var f2 = function(){console.log("f2");}; var callbacks = $.Callbacks(); callbacks.add(f1); callbacks.add(f2); callbacks.fire();//输出 f1 f2 callbacks = $.Callbacks("memory stopOnFalse"); callbacks.add(f1); callbacks.add(f2); callbacks.fire();//只输出 f1 callbacks.add(function(){console.log("f3");}); //不会输出,memory已经失去作用了 callbacks.fire();//重新触发,输出f1 }); </script>
?
当然,Callbacks还提供了类似remove,disable,lock等方法,可以查看文档,就不一一介绍了,最主要的还是上边的核心思想以及四种控制其行为的状态标志位。
?
三.延迟队列(Deferred)
?
???????? Deferred是一个延迟队列,基于上边提到的Callbacks,其实Callbacks本身就已经拥有延迟功能了(还记得我们要自己fire才可以触发函数列表吧)。而Deferred内部包含了三个Callbacks,它们的定义分别如下:
var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList= jQuery.Callbacks( "memory" ),
可见,doneList和failList只可以触发一次,而progressList可以触发多次,而这个Deferred可以用来作啥?我们先来看段代码:
<script> $(function($){ //xhr你可以认为就是一个Deferred var xhr = $.ajax({ url : "test.html", //此success函数会添加到内部的doneList中去 success : function(){ console.log("success"); }, //此error函数会添加到内部的failList中去 error : function(){ console.log("error"); } }); xhr.success(function(){ console.log("another success"); //既然success添加到doneList中去,而它又具有"once memory"标识,所以这里添加进去的函数会马上执行 xhr.success(function(){ console.log("aftersuccess"); }); }).error(function(){ console.log("anothererror"); }); //如果请求成功,输出结果将是 "success" "anothersuccess" "after success" //如果请求失败,输出结果将是 "error""another error" }); </script>
?
??????? 其实,Deferred的设计刚好用 在ajax上,在进行ajax请求的时候,我们往往要添加成功和失败的回调处理,而且有时并不止一个,所以内部用了doneList和failList两 个Callbacks,至于progressList是个比较特殊的东西,它相当于一个预处理的功能,在doneList和failList触发之前触 发,而且必须由用户自己去触发它,所以一般我们不用在意它。
?
?????? 那么有了Deferred,ajax请求怎么调用那些回调就显而易见了,只要在ajax请求后根据状态判断是成功还是失败,然后选择触发doneList或者failList就行了,没什么其它稀奇的事。
?
?????? 最 后再讲一下Deferred的promise方法,它是个挺好的概念来的,为了更好的理解它,我们回到上边的ajax实例。我们知道,doneList和 failList应该由ajax内部来进行触发,而你拥有的功能仅仅是加入函数而已,毕竟只有ajax自己才知道请求成功还是失败了,那如果你拿到了上边 的xhr变量后偏偏要自己去? xhr.fire(),不就可以破坏ajax的回调处理了?
?
????? 虽然我想作为一个正常的开发者没人会这样去做吧,但jQuery作者还是留了后招,上边虽然我注释到 xhr? 基本可以认为是一个Deferred,但严格上说并不完全是,它是调用了Deferred.promise()返回的一个弱Deferred,此 Deferred弱的地方就在于它没有fire/fireWith这样的方法,所以jQuery作者想得还是蛮周到的吧,当你想用Deferred开发自 己的东西时,也许也会利用到这一个方法的。
?
???? 总的说就是,Deferred.promise可以造出一个Deferred,但此Deferred只可以添加函数,不可以执行真正的触发。
?
四.最后的话
?
???????? jQuery的Deferred是一个设计挺巧妙的东西,如果你想实现自己的异步的队列,完全可以参考它的设计,关于所要掌握的一些概念,在上边都已经提 到了,这只是一个初端,但对你进一步去了解它我觉得还是有好处的,特别是如果你想研究它的代码实现,有了这些知识,就不会觉得非常难以理解了。
?
?
五. 附录: Callbacks源码注释
?
/* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * 表示函数列表只有第一次fire()/fireWith()起作用 * * 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) * 在调用add()添加函数时,如果函数列表已经触发过并执行完毕,只要设置了此值,新添加进去的函数将马上执行,否则不执行 * * unique: will ensure a callback can only be added once (no duplicate in the list) * 设置了此值表示函数列表中的函数不可以重复(两个不同引用指向同个函数意为重复) * * stopOnFalse: interrupt callings when a callback returns false * 设置了此值,一旦某个函数返回了false,其后的函数将不会执行,但如果flags.once=false,那么再次 * 调用fire还是可以再次触发的 * */ //flags的值比空白隔开 ,比 "once memory" "unique stopOnFalse" jQuery.Callbacks = function( flags ) { // Convert flags from String-formatted to Object-formatted // (we check in cache first) //最终flags将会是一个对象,比如{once:true , unique:true} flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // Actual callback list //存放函数的数组 list = [], // Stack of fire calls for repeatable lists //如果flags.once=true,那么当你调用fire时,而且这时上一轮的fire还没有执行完毕,这时候将会把 [context , args]入栈 //然后等上一次fire完结后,它会继续判断stack里边是否还有值,有的话会拿出[context , args]再次执行所有的函数列表 stack = [], // Last fire value (for non-forgettable lists) //默认值为false,表示此函数列表还没有触发过,一旦此函数列表触发过了,将有两种可能的情况: //如果flags.once=true,那么memory=true,以后在fire时,一发现memory=true,将什么也不做 //如果flags.memory=true,那么memory=[context , args] memory, // Flag to know if list is currently firing //是否正在触发中 firing, // First callback to fire (used internally by add and fireWith) //从函数列表哪里开始执行 firingStart, // End of the loop when firing //执行的函数列表长度,也就是执行函数的个数 firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // Add one or several callbacks to the list //真正执行添加函数的内部方法 add = function( args ) { var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); //传递了数组同样可以处理 if ( type === "array" ) { // Inspect recursively add( elem ); } else if ( type === "function" ) { // Add if not in unique mode and callback is not in //处理flags.unique,如果flags.unique=true,还要判断一下是否函数已经存在了 if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); } } } }, // Fire callbacks //真正执行触发函数列表的方法 fire = function( context, args ) { args = args || []; //默认情况下,flags.memory=false,这时memory=true,表示已经执行过了 //如果flags.memory=true,memory=[context , arsg] memory = !flags.memory || [ context, args ]; firing = true; //下边为什么要这么麻烦呢,用三个参数来控制. //在add的时候,如果flags.memory=true,并且函数列表执行过了,这时候只要执行新的函数就行了, //而在add中会设置firingStart为合适的值,这样自然就只执行最后添加的函数了 firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { //这是唯一处理flags.stopOnFalse的地方 if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { //只要flags.stopOnFalse=true,并且函数返回了false,那么memory强制设为true,这时候flags.memory将会失去作用 //因为flags.memory=true的作用就是 设置memory=[context , args] memory = true; // Mark as halted break; } } firing = false; //当触发执行完毕后,还要检查一下stack的情况,处理flags.once=false,并且调用了多次的fire() if ( list ) { if ( !flags.once ) { if ( stack && stack.length ) { memory = stack.shift(); //memory=[context , args]在这里将用到了 self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { //既然已经触发过了,并且没有额外的控制,如没有设置flags.once=false,没有设置flags.memory=true,那么作废该 Callbacks吧 self.disable(); } else { list = []; } } }, // Actual Callbacks object //这就是调用 jQuery.Callbacks()返回的对象,用户眼中的Callbacks实例 self = { // Add a callback or a collection of callbacks to the list add: function() { //只要未曾 disable此Callbacks,此条件将永远为true if ( list ) { var length = list.length; add( arguments );//调用真正的函数添加处理 // Do we need to add the callbacks to the // current firing batch? //如果当前Callbacks正在触发,那么只要修改firingLength的值就可以了,这样刚添加进去的函数自然就会执行了 if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) //在添加的时候,只有一种情况会触发函数列表,那就是flags.memory=true,而且 //如果flags.stopOnFalse=true,并且有函数返回了false,那么即使 //flags.memory=true,这时候memory也会被强制设置为true,flags.memory完全失去了作用。 } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; }, // Remove a callback from the list //有添加,自然也可以删除函数 remove: function() { if ( list ) { var args = arguments, argIndex = 0, argLength = args.length; for ( ; argIndex < argLength ; argIndex++ ) { for ( var i = 0; i < list.length; i++ ) { //找到要删除的函数了,这时候要看Callbacks是否真在执行,若真在执行,要修改一些内部状态 if ( args[ argIndex ] === list[ i ] ) { // Handle firingIndex and firingLength if ( firing ) { if ( i <= firingLength ) { firingLength--; //firingIndex表示正在执行函数的索引,如果 //要删除的函数在这个索引前面,说明firingIndex必须减1,然后把前边那个函数删了 if ( i <= firingIndex ) { firingIndex--; } } } // Remove the element list.splice( i--, 1 ); // If we have some unicity property then // we only need to do this once //如果函数是唯一的,那么重新开始下一个函数的删除 if ( flags.unique ) { break; } } } } } return this; }, // Control if a given callback is in the list //一个给定函数是否已经在函数列表中了 has: function( fn ) { if ( list ) { var i = 0, length = list.length; for ( ; i < length; i++ ) { if ( fn === list[ i ] ) { return true; } } } return false; }, // Remove all callbacks from the list //纯粹的清空而已,不影响函数列表的添加和触发 empty: function() { list = []; return this; }, // Have the list do nothing anymore //一经disable,这个函数列表就完全不起作用了,相当于废了,可以休息了 disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? //判断函数列表是否已经disable了 disabled: function() { return !list;//list==undefined就说明该函数列表废了 }, // Lock the list in its current state //一旦上了锁,stack=undefined,那么即使flags.once=false,调用再多的fire也是无济于事,其实Callbacks也相当于废了 lock: function() { stack = undefined; if ( !memory || memory === true ) { 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 ( stack ) { if ( firing ) { if ( !flags.once ) { //如果当前Callbacks正在触发,你这时又调用了一次fire,这时候只要flags.once=false,那么就会把[context , args]入栈 //这样当上一次的触发完成时,将会检查stack,然后入栈自动重新执行函数列表 stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { //把条件转换成 !flags.once || !memory感觉好懂一些 //如果flags.once=false,那么继续触发吧 //或者flags.once=true,但 memory=false,表示还没触发过,那么执行第一次的触发吧 fire( context, 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 //memory=false表示还没有触发,其它值均表示触发过了 fired: function() { return !!memory; } }; return self; };
?
?