jQuery1.7系列三: jQuery延迟列表(Deferred)

来源:互联网 发布:淘宝上为什么没有熙然 编辑:程序博客网 时间:2024/05/29 16:05

           声明:写博客,是对自身知识的一种总结,也是一种分享,但由于作者本人水平有限,难免会有一些地方说得不对,还请大家友善  指出,或致电: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 recursivelyadd( 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 haltedbreak;}}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 listadd: function() {//只要未曾 disable此Callbacks,此条件将永远为trueif ( 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 firingLengthif ( firing ) {if ( i <= firingLength ) {firingLength--;//firingIndex表示正在执行函数的索引,如果//要删除的函数在这个索引前面,说明firingIndex必须减1,然后把前边那个函数删了if ( i <= firingIndex ) {firingIndex--;}}}// Remove the elementlist.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 argumentsfireWith: 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 argumentsfire: 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;};

原创粉丝点击