zoukankan      html  css  js  c++  java
  • jQuery-1.9.1源码分析系列(六) 延时对象

      首先我们需要明白延时对象有什么用?

      第一个作用,解决时序以及动态添加执行函数的问题。

    function a(){alert(1)};
    function b(){alert(2)};
    function c(){alert(3)};
    a();
    setTimeout(function(){b();},0);
    c();

      很明显函数执行顺序是a->c->b,而不是按照函数添加的顺序执行。如果我要保证函数按顺序执行,那么c 必须写紧跟在b后面执行

    setTimeout(function(){b(); c();},0);

      如果添加的函数按顺序执行,比如添加在b后面的函数都在b执行后执行(如果b已经执行过了,那么久马上执行)。如果按照这种方式,我们必须在执行之前就知晓说有要执行的函数,然后都加到setTimeout里面去,这个限制非常可恶。我希望的是即使b执行过了,我后面还能动态的添加函数并得到执行。比如

    var defer = $.Deferred(); //构建异步对象
    function a(){alert(1)};
    function b(){alert(2)};
    //添加函数
    defer.done(
    a);
    //添加函数
    defer.done(b);
    setTimeout(function(){
      defer.resolve();//alert(1),alert(2)
    },0);
    //添加函数
    defer.done(function c(){alert(3)});//马上执行出结果alert(3)

      第二,解决参数传递的问题,所有的执行函数需要的参数相同,我希望我只传递一个参数就将所有的结果执行出来,特别是后续添加的函数执行我也希望使用原来的参数。

    function a(value){alert("a = " + value)};
    function b(value){alert("b = " + value)};
    
    var list = [],
        val = 0;;
    list[list.length] = a;
    list[list.length] = b;
    
    function runList(listArray,value){
        for(var i = 0; i < listArray.length; i++){
            listArray[i](value);
        }
        listArray.length  = 0;
    }
    val = 5;
    runList(list,val );//执行a = 5;b = 5;
    
    list[list.length] = function c(value){alert("c = " + value)};
    runList(list,val);//执行结果c = 5

      在保证runList函数有自己的作用域,不使用外部变量的情况下,那么我们每次执行runList都需要重新传递list和val这个变量。这种重复劳动没有任何意义。看看deferred的处理方式

    var defer = $.Deferred(); //构建异步对象
    function a(value){alert("a = " + value)};
    function b(value){alert("b = " + value)};
    
    defer.done(a,b);//添加函数
    
    defer.resolve(5);//执行结果a = 5; b = 5;
    
    defer.done(function c(value){alert("c = " + value)});//添加函数,执行结果c = 5

      首先你要承认的是代码简化和可读性都高很多,其次,传递的执行参数值5只需要一次便足够,哪怕是后面添加的函数c也会用先前传递过来的参数执行。

      Deferred函数将Callbacks函数抽离出去以后Deferred函数变得很精简。如下

    jQuery.extend({
        Deferred : function(){…}
        when : function(){…}
    )}

      其中Deferred的整体结构如下

      Deferred: function( func ) {
           var tuples = [
                  // action, add listener, listener list, final state
                  [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                  [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                  [ "notify", "progress", jQuery.Callbacks("memory") ]
        ],
        state = "pending",
        promise = {
          state: function() {…},
          always: function() {…},
          then: function( /* fnDone, fnFail, fnProgress */ ) {…},
          promise: function( obj ) {…}
        },
        deferred = {}; 
    
           //兼容老版本
           promise.pipe = promise.then; 
    
           // 添加promise的三个方法[done | fail | progress]方法
        // 添加deferred的六个方法[resolve |resolveWith | reject | rejectWith | notify | notifyWith]
    jQuery.each( tuples, function( i, tuple ) {       ...     });     // Make the deferred a promise     promise.promise( deferred );     ...     return deferred;   }

      主要做了几个工作。

    1. 定义promise的几个对外接口state/always/then/promise
    2. 通过tuples初始化deferred的几个接口resolve/resolveWith/reject/rejectWith/notify /notifyWith,并且为promise定义了另外的三个接口done/fail/progress。其中会被添加到这些个接口都与tuples创建的三个Callbacks有关。后续再详解。
    3. 使用promise.promise拓展deferred返回。我们看一下这个返回有哪些个方法  

    deferred.done(fns)

    将回调添加到成功回调列表(doneCallbacks),当Deferred(延迟)对象解决(调用resolve)时会执行成功回调列表中的所有函数。参数fns可以是函数,也可以是函数数组

     deferred.fail(fns)

     将回调添加到失败回调列表(failCallbacks),当Deferred(延迟)对象拒绝(调用reject)时会执行失败回调列表中的所有函数。参数fns可以是函数,也可以是函数数组

    deferred.always(fns)

    将回调添加到成功回调列表(doneCallbacks和失败回调列表(failCallbacks)。确保当Deferred(延迟)对象解决(调用resolve)或拒绝(调用reject)时,都会执行到该回调。参数fns可以是函数,也可以是函数数组

    deferred.progress(fns)

     将回调添加到进度回调列表(progressCallbacks),当Deferred(延迟)对象生成进度通知(调用notify)时,执行进度回调列表中的所有函数。参数fns可以是函数,也可以是函数数组

    deferred.resolve(args)

     解决Deferred(延迟)对象,并根据给定的args参数执行成功回调列表(doneCallbacks)的所有函数。

    deferred.resolveWith(context, args)

     解决Deferred(延迟)对象,并根据给定的 context和args参数执行成功回调列表(doneCallbacks)的所有函数。这是jQuery.Callbacks的内部方法fireWith的引用,deferred.resolve()方法内部调用该方法来实现。不建议外部使用。

    deferred.reject(args)

     拒绝Deferred(延迟)对象,并根据给定的args参数调用失败回调列表(failCallbacks)中的所有函数。

    deferred.rejectWith(context, args)

     拒绝Deferred(延迟)对象,并根据给定的 context和args参数执行失败回调列表(failCallbacks)的所有函数。这是jQuery.Callbacks的内部方法fireWith的引用,deferred.reject()方法内部调用该方法来实现。不建议外部使用。

    deferred.notify(args)

     根据给定的 args参数 调用Deferred(延迟)对象上进度回调列表(progressCallbacks)的所有函数。

    deferred.notifyWith(contex,args)

     根据给定的上下文(context)和args递延调用Deferred(延迟)对象上进度回调列表(progressCallbacks )上的所有函数。这是jQuery.Callbacks的内部方法fireWith的引用,deferred.notify()方法内部调用该方法来实现。不建议外部使用。

    deferred.state()

     确定一个Deferred(延迟)对象的当前状态。"pending"、"resolved"、"rejected"

    deferred.pipe()/deferred.then()

     当Deferred(延迟)对象解决(resolve)、拒绝(reject)或生成进度(notify),调用相应回调列表的所有函数。

    jQuery.Deferred()

      一个构造函数,返回一个延时对象。

    jQuery.when(subordinate /* , ..., subordinateN */)

     提供一种方法来执行一个或多个对象的回调函数, Deferred(延迟)对象通常表示异步事件。

    .promise()

     返回一个 Promise 对象用来观察当某种类型的所有行动绑定到集合,排队与否还是已经完成

      

    a. $.Deferred()详解


      关键源码如下

    Deferred: function( func ){
      var tuples = [
        // 执行名, 添加监听器(回调), 监听列表(回调列表), 最终状态
        [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
        [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
        [ "notify", "progress", jQuery.Callbacks("memory") ]
      ],
      state = "pending",
      promise = {
        ...
        promise: function( obj ) {
          return obj != null ? jQuery.extend( obj, promise ) : promise;
        }
      },
      deferred = {};
      
      //
    添加列表指定的方法   jQuery.each( tuples, function( i, tuple ) {     var list = tuple[ 2 ],     stateString = tuple[ 3 ];     // promise[ done | fail | progress ] = list.add     promise[ tuple[1] ] = list.add;     // 只有tuples[0]和tuples[1]进入该分支     if ( stateString ) {       //给Callbacks对象添加回调列表       list.add(function() {         // state = [ resolved | rejected ]         state = stateString;         // [ reject_list | resolve_list ].disable; progress_list.lock       }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );     }     // deferred[ resolve | reject | notify ]     deferred[ tuple[0] ] = function() {       deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );       return this;     };     deferred[ tuple[0] + "With" ] = list.fireWith;   });   // 给deferred添加promise的函数接口   promise.promise( deferred );   ...
      return
    deferred; }

      我们把tuples[index][2]中的jQuery.Callbacks返回的对象所代表的回调列表分别取名叫做doneCallbacks(成功回调列表),failCallbacks(失败回调列表),progressCallbacks(进度回调列表)。则到函数执行到promise.promise( deferred );之前。promise变量变成了

    promise = {
        state: function() {…},
        always: function() {…},
        then: function( /* fnDone, fnFail, fnProgress */ ) {…},
        promise: function( obj ) {…},
        done: doneCallbacks.add,
        fail: failCallbacks.add,
        progress: progressCallbacks.add
    }

      deferred变量变成了

    deferred = {
        resolve: function() {
            deferred["resolveWith" ]( this === deferred ? promise : this, arguments );
            return this;
        },
        resolveWith: doneCallbacks.fireWith,
        reject: function() {
            deferred["rejectWith" ]( this === deferred ? promise : this, arguments );
            return this;
        },
        rejectWith: failCallbacks.fireWith,
        notify: function() {
            deferred["notifyWith" ]( this === deferred ? promise : this, arguments );
            return this;
        },
        notifyWith: progressCallbacks.fireWith
    }    

      初始化结束后doneCallbacks中回调队列如下

    [
        function(){state = “resolved”:},
        failCallbacks.disable,
        progressCallbacks.lock
    ]

      初始化结束后failCallbacks中回调队列如下

    [
        function(){state = “rejected”;},
        doneCallbacks.disable,
        progressCallbacks.lock
    ]

      初始化结束后progressCallbacks的回调列表中没有任何回调

      需要注意的是doneCallbacks、failCallbacks的设置是”once memory”,progressCallbacks设置是”memory”

      这样,应当好理解Deferred的原理了。我们使用done/fail是”once memory”设置下的add。意味着如果如果先执行了resolve/ reject方法,后调用done/fail则会直接执行回调。

      eg:

    var defer = $.Deferred(); //构建异步对象
    //执行doneCallbacks的回调,保存场景,这个时候doneCallbacks有三个回调函数,依序执行。其中第二个回//调禁用failCallbacks(禁用reject/rejectWith),第三个回调锁progressCallbacks(锁notify/notifyWith) defer.resolve( 5 ); defer.done(function( value ) {   console.log('打印出值',value) });//doneCallbacks.add,立刻执行回调

      如果是先调用done/fail,后执行resolve/reject。根据”once memory”将会先添加回调,然后fire。

      eg:

    var defer = $.Deferred(); //构建异步对象 
    
    //1秒钟后执行doneCallbacks回调,保存场景。这个时候的doneCallbacks有四个回调函数,依序执行
    setTimeout(function(){
      defer.resolve( 5 );   
    },1000); 
    
    //根据”once memory”,第一次fire之前会把回调添加到doneCallbacks中。等待调用
    defer.done(function( value ) {console.log('打印出值',value)});

      当延迟对象被 resolved 时,任何通过 deferred.then或deferred.done 添加的 doneCallbacks,都会被调用。回调函数的执行顺序和它们被添加的顺序是一样的。传递给 deferred.resolve() 的 args 参数,会传给每个回调函数。当延迟对象进入 resolved 状态后,再添加的任何 doneCallbacks,当它们被添加时,就会被立刻执行,并带上传入给 .resolve()的参数

       

     b. Deferred.promise.then详解


      API是这么定义的:

      deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

      从jQuery 1.8开始, 方法返回一个新的promise(承诺),通过一个函数,可以过滤deferred(延迟)的状态和值。替换现在过时的deferred.pipe()方法。   doneFilter 和 failFilter函数过滤原deferred(延迟)的解决/拒绝的状态和值。 progressFilter 函数过滤器的任何调用到原有的deferred(延迟)的notify 和 notifyWith的方法。

      这些过滤器函数可以返回一个新的值传递给的 promise(承诺)的.done() 或 .fail() 回调,或他们可以返回另一个观察的对象(递延,承诺等)传递给它的解决/拒绝的状态和值promise(承诺)的回调。

      如果过滤函数是空,或没有指定,promise(承诺)将得到与原来值相同解决(resolved)或拒绝(rejected)。

      

      源码解析:

    then: function( /* fnDone, fnFail, fnProgress */ ) {
      var fns = arguments;
      return jQuery.Deferred(function( newDefer ) {
           //遍历tuples,将fnDone, fnFail, fnProgress分别绑定到tuples[0-2][2]的回调列表上
        jQuery.each( tuples, function( i, tuple ) {
          var action = tuple[ 0 ],//resolve/reject/notify
          fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
          //执行deferred[ done | fail | progress ] 函数,将回调fnDone, fnFail, fnProgress分别添加到回调列表
          //需要明确的是这些回调函数是添加到deferred中的,不是newDefer中的。
          deferred[ tuple[1] ](function() {
            //执行fnDone, fnFail, fnProgress函数
            var returned = fn && fn.apply( this, arguments );
            //如果返回的是Deferred对象或是Deferred.promise对象
            if ( returned && jQuery.isFunction( returned.promise ) ) {
              //当Deferred(延迟)对象解决/拒绝/生成进度通知时
              //调用添加处理程续resolve/reject/notify
              returned.promise()
              .done( newDefer.resolve )
              .fail( newDefer.reject )
              .progress( newDefer.notify );
            } else {
              //执行resolveWith/rejectWith/notifyWith函数执行所有剩余回调
              newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
            }
          });
        });
        fns = null;
      }).promise();
    }。

      我们抓住几点:

      返回的是新的promise对象

      内部有一个过滤器函数

      我们将最外层初始化的延时对象叫做主延时对象mainDeferred,经过mainDeferred.then()方法处理的代码中返回的jQuery.Deferred(...).promise(),不是原来的mainDeferred了,我们把它命名为thenDeferred。thenDeferred初始化的时候有这个一段

                // 调用给定的func参数
                if ( func ) {
                    func.call( deferred, deferred );
                }

      刚好thenDeferred的初始化带有函数参数我们命名为thenParamfunc。thenParamfunc的参数是newDefer,结合上面执行func的代码我们可知,这个newDefer就是thenDeferred嘛,饶了半天。thenParamfunc中做一个操作:

      往mainDeferred的三个回调列表中添加回调,每个回调内部会执行mainDeferred.then添加的对应的回调函数。

          //deferred是主延时对象mainDeferred
          deferred[ tuple[1] ](function() {
            
    //执行fnDone, fnFail, fnProgress函数
            
    var returned = fn && fn.apply( this, arguments );
            
    ...
          });

      还需要注意的是当这些回调被调用时,mainDeferred.then添加的回调执行后,会执行下面的代码

    newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );

      这表明mainDeferred.then生成的延时对象thenDeferred的状态依赖于主延时对象mainDeferred。当mainDeferred解决(resolve),那么thenDeferred也会被解决(resolve),并且mainDeferred.then设置的doneFilter的返回值将作为参数传递给thenDeferred的回调列表。

      eg:

    var deferred = $.Deferred();
    
    var m = deferred
    .done(function(val){console.log('done function ' + val)})
    .then(function(val){console.log('done then ' + val); return 10;},
      function(val){console.log('fail then ' + val);},
      function(val){console.log('progress then' + val);})
    //主延时对象解决
    //打印done function 5;
    done then 5;
    deferred.resolve(5);

    //执行then deferred done 10
    m.done(function(val){console.log("then deferred done " + value)})

      

      现在完整的分析一个例子的数据

      eg:

    var deferred = $.Deferred();
    
    deferred
    .done(function(val){console.log('done function ' + val)})
    .then(function(val){console.log('done then' + val);},
      function(val){console.log('fail then' + val);},
      function(val){console.log('progress then' + val);})
    deferred.resolve(
    5);

      说明:

           deferred.done(...).then(...)执行后,done的回调列表doneCallbacks是下面的样子

    doneCallbacks = [
      function () {     // state = [ resolved | rejected ]     state = stateString;     // [ reject_list | resolve_list ].disable; progress_list.lock   },   function () {     list = stack = memory = undefined;     return this;   },   function () {     stack = undefined;     if ( !memory ) {       self.disable();     }     return this;   },   function (val){     console.log('done function ' + val)   },   function () {     var returned = fn && fn.apply( this, arguments );     if ( returned && jQuery.isFunction( returned.promise ) ) {       returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify );     } else {       newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );     }   }
    ]

      其中doneCallbacks前面三个是Callbacks自带的,第四个是.done添加的,

      第五个这个是.then的第一个参数添加的,对应的fn是function(val){console.log('done then' + val);}

      fail的回调列表failCallbacks的样式是

      failCallbacks = [
        function () {       // state = [ resolved | rejected ]       state = stateString;   // [ reject_list | resolve_list ].disable; progress_list.lock }, function () { list = stack = memory = undefined; return this; }, function () { stack = undefined; if ( !memory ) { self.disable(); } return this; }, function () { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }
    ]

      前面三个元素是Callbacks自带的,

      第四个是.then的第二个参数添加的,对应的fn是function(val){console.log('fail then' + val);}

      progress对应回调列表progressCallbacks的样子是

    progressCallbacks = [
        function () { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }
    ]

      只有一个元素是.then的第三个参数添加的,fn对应为function(val){console.log('progress then' + val);})

       

      这一章有点长了,先到这里,下一章 分析$.when

  • 相关阅读:
    转载.net泛型理解说明
    转载Repository 和Unit of work的使用说明
    libtool的工作原理
    带有通配符的字符串匹配算法-C/C++
    linux core文件机制
    grep多条件和sed合并两行
    BZOJ 3232 圈地游戏 (分数规划 + SPFA找负/正环)
    CSP2019 D1T3 树上的数 (贪心+并查集)
    CSP-S 2019 第二轮 退役记
    object-c中的int NSInteger NSUInteger NSNumber辨析
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/jQuery-1-9-1-Deferred.html
Copyright © 2011-2022 走看看