• 第三十三课:jQuery Deferred详解1


    之前我们讲了Mochikit Deferred,JSDeferred,现在讲jQuery Deferred。首先,我们先来讲下他们的区别:

    在保存回调函数时,Mochikit Deferred(dojo Deferred)是用一个2维数组保存的,里面的小数组只有两项,一个是成功回调的函数,一个是失败回调的函数。

    JSDeferred则每个实例都必有ng,ok这两个回调函数。

    jQuery Deferred则一个_Deferred负责添加成功回调,一个负责添加错误回调。

    它们的API区别如下图:

    接下来,我们来看一下jQuery1.51的源码:

    此版本jQuery的Deferred对象的实现,是使用了一个双链结构,每条链都由_Deferred方法返回的deferred对象实现。

    var promiseMethods = "then done fail isResolved isRejected promise".split(" "),

      sliceDeferred = [].slice;

    jQuery.extend({

      _Deferred: function(){   //jQuery的静态方法,在Deferred中被调用,请先看Deferred方法。

        var callbacks = [], fired, firing,cancelled,

          deferred = {   //调用jQuery._Deferred()方法,返回的对象

            done:function(){    //根据下面的例子:var cb = $.Deferred();done方法中传入的参数是failDeferred.cancel,其实就是deferred.cancel。

              if(!cancelled){    //如果没有被取消,就进入if语句,刚开始是undefined,取非,所以是true,进入if语句。

                var args = arguments, i , length, elem, type, _fired;

                if(fired){   //刚开始是undefined,不进入if语句。

                  _fired = fired;

                  fired = 0;

                }

                for(i=0,length = args.length ;i<length; i++){   //把传入done方法的参数遍历,这里就是failDeferred.cancel

                  elem = args[i];

                  type = jQuery.type(elem);     //判断每一个参数的类型

                  if(type === "array"){   //如果是数组,就重新调用deferred.done方法。

                    deferred.done.apply(deferred,elem);

                  }else if(type === "function"){   //如果参数的类型是函数,就把函数添加到callbacks数组中。这里的failDeferred.cancel是函数,所以执行这里。

                    callbacks.push(elem);   //这时callbacks = [failDeferred.cancel]

                  }

                }

                if(_fired){    //undefined,不进入if语句。

                  deferred.resolveWith(_fired[0], _fired[1]);

                }

              }

              return this;   //this就是调用done方法的对象

            },

            resolveWith:function(context ,args){

              if(!cancelled && !fired && !firing){  //根据例子来讲解的,如果没取消,也没触发,也没在触发中,那么就进入if语句进行触发。

                args = args || [];      

                firing = 1;     //改变状态,使这个Deferred处于触发中状态

                try{

                  while(callbacks[0]){    //callbacks = [failDeferred.cancel , fun1]

                    callbacks.shift().apply(context, args); //shift方法,把数组的第一项删除,并返回第一项。这里执行failDeferred.cancel方法。因为调用了apply方法,其实就是在cb的执行上下文中执行failDeferred.cancel方法。但是由于在cancel方法中,不是针对this操作,而是针对私有变量callbacks操作,因此执行failDeferred.cancel方法时操作的还是failDeferred对象中的callbacks。所以这时,failDeferred对象中的私有变量callbacks = [deferred(局部)的cancel]会变成[]。这意味着cb延迟对象触发了resolve方法(成功触发)。这时cb的callbacks = [ fun1],然后继续循环,取出fun1,执行。然后cb和failDeferred的callbacks数组都成为[]。

                  }

                }finally{

                  fired = [context, args];

                  firing = 0;

                }

              }

              return this;

            },

            resolve:function(){

              deferred.resolveWith(this, arguments);   //resolve方法其实就是执行resolveWith方法,这里的deferred其实就是cb。因为执行var cb = $.Deferred()时,cb引用了deferred对象,callbacks等属性。所以这些变量和属性都会保存在内存中,因此你调用cb.resolve时,执行的deferred的方法,其实就是cb的resolveWith方法。而里面的callbacks属性就是cb对象的callbacks属性。

              return this;

            },

            isResolved:function(){

              return !!(firing || fired);

            },

            cancel:function(){

              cancelled = 1;

              callbacks = [];

              return this;

            }

          };

          return deferred;

      },

      Deferred:function(func){     //也是jQuery的静态方法,它就是jQuery的Deferred。里面有两个jQuery._Deferred()方法返回的deferred对象

         var deferred = jQuery._Deferred(),   //执行jQuery的静态方法,返回一个deferred对象。因为这里的局部变量deferred跟jQuery._Deferred()返回的对象deferred重名,因此我们叫局部变量deferred称为deferred(变量)

          failDeferred = jQuery._Deferred(),  //同样返回一个deferred对象。跟上面那个deferred对象不是同一个,但是具有相同的方法和属性。

            promise;

        jQuery.extend(deferred,{    //扩展deferred变量引用的deferred对象,而不是failDeferred引用的deferred对象。deferred对象默认有done,resolveWith,resolve,isResolved,cancel五个方法。deferred(变量)对象扩展后,又增加了then,fail,rejectWith,reject,isRejected,promise等六个方法。

          then : function(doneCallbacks , failCallbacks){

             deferred.done(doneCallbacks).fail(failCallbacks);

             return this;

          },

          fail:failDeferred.done,   //当deferred(变量)对象调用fail方法添加失败回调函数时,其实调用的是代表失败的failDeferred对象done方法。

          rejectWith:failDeferred.resolveWith,

          reject:failDeferred.resolve,   //当deferred(变量)对象触发失败回调方法时,也就是触发fail方法添加的回调函数时,就会调用代表失败的failDeferred对象的resolve方法,而这个方法会执行failDeferred对象中通过done方法添加的回调函数。这样就实现了Deferred对象的成功回调和失败回调的操作(内部通过两个链,一个链代表成功回调,一个链代表失败回调)。也就是说,失败回调交给failDeferred对象处理,成功回调交给deferred(变量)对象自己处理,这个deferred(变量)对象就是$.Deferred()返回的对象。

          isRejected:failDeferred.isResolved,

          promise:function(obj){    //这是一个单例方法,无论调用多少次这个promise方法,都只会返回同一个对象。

            if(obj == null){

              if(promise){

                return promise;

              }

              promise = obj = {};

            }

            var i = promiseMethods.length;

            while(i--){

              obj[promiseMethods[i]] = deferred[promiseMethods[i]];    //Promise对象有then,done,fail,isResolved,isRejected,promise六个方法,它没有触发回调的方法,因此Promise对象只能添加回调方法,不能触发回调方法的执行。

            }

            return obj;

          }

        });

        deferred.done(failDeferred.cancel).fail(deferred.cancel);  //调用deferred(变量)的done方法和fail方法。我们去看一下这两个方法,请去看_Deferred()。当执行完done方法后,deferred(变量)对象中的私有变量callbacks = [failDeferred.cancel],返回deferred(变量),再调用它的fail方法。它的fail方法是failDeferred.done方法。而failDeferred.done方法就是deferred对象的done方法,传入的参数是deferred(变量)cancel方法。执行完deferred(变量)的fail方法后,deferred(变量)对象中的私有变量callbacks = [failDeferred.cancel]。而failDeferred对象中的私有变量callbacks = [deferred(局部)的cancel]。

        delete deferred.cancel;   //删除deferred(局部)的cancel方法。

        if(func){

          func.call(deferred,deferred);   //举个例子:如果在var cb = $.Deferred();中传入了一个函数func,那么就会在deferred(局部)对象的执行上下文中执行这个函数,并且把此对象作为参数传给函数func。

        }

        return deferred;   //返回deferred(局部)对象。

      },

      when:function(firstParam){

        var args = arguments, i =0, length = args.length, count = length;

        var deferred = length <=1 && firstParam && jQuery.isFunction(firstParam.promise) ? firstParam : jQuery.Deferred();  //如果只传入了一个Deferred或Promise对象。,就直接使用此对象,否则生成一个新的Deferred对象。

        function resolveFunc(i){   //定义私有函数

          return function(value){

            args[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0 ) : value;   //sliceDeferred就是[].slice方法。args[0]是cb1.resolve()传入的参数值,args[1]是cb2.resolve()传入的参数值。

            if(!(--count)){   //如果传入when中的所有Deferred都触发了resolve方法,这里的count才会变成0,因此为真,触发cb3的resolve方法。

              deferred.resolveWith(deferred,sliceDeferred.call(args,0));

            }

          }

        }

        if(length > 1){    //如果传入了2个以及2个以上的Deferred对象

          for(; i < length ;i++){

            if(args[i] && jQuery.isFunction(args[i].promise)){  //如果传入的参数是Deferred对象

              args[i].promise().then(resolveFunc(i), deferred.reject);   //取到Deferred的Promise对象,调用它的then方法。then方法是:function(doneCallbacks , failCallbacks){ deferred.done(doneCallbacks).fail(failCallbacks); return this; },then方法可以同时添加成功回调和失败回调。这里举个例子:var cb1 = $.Deferred(); var cb2 = $.Deferred();    var cb3 = $.when(cb1 , cb2).done( function fun3(){ cb1和cb2都触发成功了 } );  cb1的成功回调数组callbacks=[resolveFunc[0]],失败的回调数组callbacks=[cb3.reject],cb2的成功回调数组callbacks=[resolveFunc[1]],失败的回调数组callbacks=[cb3.reject],cb3的成功回调数组为callbacks = [fun3]。这时,如果你触发cb1和cb2的resolve方法,那么就会触发cb3的resolve方法。如果你只触发了cb1或cb2中的其中一个resolve方法,那么cb3的resolve方法不会触发,需要等cb1和cb2的resolve方法都触发了,才会触发。如果你触发了cb1或cb2中任何一个reject方法,那么cb3的reject方法就会触发。

            }else{    

              --count;  //如果传入的都不是Deferred对象,这里的count就会减为0.如果有一个,就不会等于0.

            }

          }

          if(!count){     //如果为0,就会执行这个方法。举个例子:$.when("chaojidan","chaojidan1","chaojidan2")

            deferred.resolveWith(deferred,args);

          }

        }else if(deferred !== firstParam){       //如果没传入Deferred对象,或者传入了一个不是Deferred对象的参数。举个例子:var cb = $.when()或$.when("chaojidan")

            deferred.resolveWith(deferred,length?[firstParam] : [] );

        }

        return deferred.promise();   //如果传入了一个Deferred对象,就直接返回它的Promise对象。举个例子:var cb = $.Deferred();var cb1 = $.when(cb),这时的cb1其实是cb的Promise对象。其他情况,都返回一个新的Deferred的Promise对象。

      }

    })

    此段代码,我们根据这个例子:var cb = $.Deferred();cb.done(function fun1(){alert(1)}),cb.resolve();来讲解。从上可知,当调用了var cb = $.Deferred();方法后,cb的callbacks=[failDeferred.cancel],failDeferred对象中的私有变量callbacks = [cb.cancel],当调用cb.done(function fun1(){alert(1)})方法之后,cb的callbacks=[failDeferred.cancel , fun1],failDeferred不变,当调用cb.resolve();方法后,其实就是调用deferred.resolveWith(cb, []);请看此方法解释。最终,cb和failDeferred的私有变量callbacks都会变成[]。

    这是jQuery的第一代的Deferred模块,在jQuery1.6,1.7,1.8中,都进行优化,下节课讲解。

    加油!

  • 相关阅读:
    【掉下巴】枪的制造现场
    不引入第三个变量交换两个变量的方法
    [转]科学计算经典算法
    [小练eVC]常用控件之微调按钮
    【收购】LSI 40亿美元并购Agere
    VB6.0不支持鼠标滚轮的解决办法
    一个简单的BP网络C语言程序
    [转]想成为嵌入式程序员应知道的0x10个基本问题
    [zt]关于左值"lvalue"和右值"rvalue"的一点理解
    [掉下巴]细数非洲大山的肘下亡魂
  • 原文地址:https://www.cnblogs.com/chaojidan/p/4186956.html
Copyright © 2020-2023  润新知