美文网首页我爱编程
jQuery.Deferred 对象源码解析

jQuery.Deferred 对象源码解析

作者: Yanz2018 | 来源:发表于2018-03-29 16:59 被阅读0次

    Deferred 是什么?

    实际上 deferred 就是 Promise/A+ 标准的 jQuery 的实现,是一个管理异步操作的对象

    jQuery.Deferred 大体逻辑

    • 源码大概代码如下
    jQuery.extend(
      Deferred: function( func ) {
        // 声明各个状态的 状态转移方法、回调注册方法、回调列表对象、状态名等
        var tuples = [
          [ ... ], // 对应 fulfilled
          [ ... ], // 对应 rejected
          [ ... ]  // 对应 progress 
        ], 
          // 初始状态声明,即 等待 状态
          state = "pending", 
          
          // promise 对象,用于处理异步回调,如 done()、fail()、progress()、then() 等,是 deferred 的一个子集
          promise = { ... },
          
          // deferred 对象,声明时就是一个空对象,除了能处理异步回调,还能更改 promise 状态
          deferred = {}; 
        
        // 向 deferred 和 promise 里注入 done,fail,progress 等回调注册方法
        // 向 deferred 里注入 resolve、reject、notify 等更改状态的方法
        jQuery.each( tuples, function( tuple, i ) {
          ...
        } );
        
        // 将 promise 里的对象混合到 deferred 中
        promise.promise( deferred );
    
        // Deferred() 实际上是个工厂函数,func 可用于配置 deferred,常用于多级异步处理
        if ( func ) {
          func.call( deferred, deferred )
        }
    
        // 最后返回 deferred 对象
        return deferred;
      }
    )
    

    总结:

    1. 声明 tuples、state、promise、deferred 变量;
    2. 将 tuples 混入 deferred 中;
    3. 将 promise 混入 deferred 中;
    4. 如果有对 deferred 的配置函数 func,则执行;
    5. 最后返回 deferred 对象;

    作为配置数组的 tuples

    • 来看 tuples 的具体定义
    // tuples 包含三个数组,对应了 deferred 的三种状态,每个 tuple 是一种状态
    // tuple[ 0 ] 是状态转移方法名
    // tuple[ 1 ] 是回调注册方法名
    // tuple[ 2 ] 是通过 tuple[ 1 ] 方法注册的回调函数的容器,其中 resolve 和 reject 是单次执行的
    // tuple[ 3 ] 是通过 then() 方法注册的回调函数的容器
    // tuple[ 4 ] 只用在 then() 方法的定义中,是用于标明回调函数类型的下标
    // tuple[ 5 ] 是最终状态
    var tuples = 
      [ 
        [ "notify", "progress", jQuery.Callbacks( "memory" ),
          jQuery.Callbacks( "memory" ), 2 ],
    
        [ "resolve", "done", jQuery.Callbacks( "once memory" ), 
          jQuery.Callbacks( "once memory" ), 0, "resolved" ],
    
        [ "reject", "fail", jQuery.Callbacks( "once memory" ),
          jQuery.Callbacks( "once memory" ), 1, "rejected" ]
      ]
    

    总结:
    该数组实际上对整个 deferred 的 基本方法名、状态名、回调管理容器 进行了定义。


    promise 对象定义

    • promise 对象是 deferred 中非常关键的一个对象,它是 deferred 的子集,定义了一系列的回调注册的函数,声明时有如下成员对象:
    promise = {
      
      // 返回当前状态
      state: function() {
        return state;
      },
    
      // 注入 onFulFilled 或 onRejected 都要调用的回调函数
      always: function() {
        deferred.done( arguments ).fail( arguments );
        return this;
      },
    
      // catch 语法糖,实际就是通过 then 注入 onRejected 回调
      "catch": function() {
        return promise.then( null, fn );
      },
      pipe: function() { ... },
      then: function() { ... },
      promise: function() { ... }
    }
    

    • 下面详解 pipe 的实现
      pipe 可以理解为一个钩子函数,传参为三个回调,对状态返回结果进行预处理。
      源码如下:
    pipe: function( /* fnDone, fnFail, fnProgress */ ) {
    
      var fns = arguments;
    
      // 返回的是一个新的 deferred 对象,通过配置函数进行了改造
      // 向新的 deferred 的回调列表加入了回调函数
      return jQuery.Deffered( function( newDefer ) {
    
        jQuery.each( tuples, function( i, tuple ) {
          
          // 通过 tuple[ 4 ] 下标获取参数中对应的回调函数 fn
          var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
          
          // 在对应的回调列表中注入回调函数
          deferred[ tuple[ 1 ] ]( function() {
            
            // 先调用获取的 fn 得到 returned
            var returned = fn && fn.call( this, arguments );
    
            // returned 可能是一个 promise 或者 deferred,都有 promise 方法
            // 这种情况下,返回的新的 deferred 的状态改变应该依赖于 returned 的状态改变
            // 上一条规则参照 Promise/A+ 规范
            if ( returned && isFunction( returned.promise ) ) {
              
              // 于是就将新的 deferred 的状态转移函数注入到 returned 的回调列表中
              returned.promise()
                .progress( newDefer.notify )
                .done( newDefer.resolve )
                .fail( newDefer.reject )
            } else {
              
              // 否则,returned 就应该作为状态转移函数的入参
              // 注意,这里 this 继承了该 deferred 状态转移函数传入的上下文对象
              // 如果没有 fn,参数就为该 deferred 状态转移函数传入的参数
              newDefer[ tuple[ 0 ] + 'With' ](
                this,
                fn ? [ returned ] : arguments
              );
            }
          } );
        } );
    
        fns = null;
      } ).promise();
    }
    

    总结:

    1. pipe 函数用传入的回调对原来的 deferred 进行了装饰,返回一个新的 deferred 对象;
    2. 如果回调的返回值也是一个异步对象,如 promise 或 deferred,那么新的 deferred 的状态依赖于这个返回值的状态;
    3. 如果返回值是一个非异步对象,就直接作为新 deferred 的状态转移函数的参数;
    4. 新 deferred 的状态转移函数的上下文继承老的 deferred 的状态转移函数的上下文;
    5. 如果未传入回调,新 deferred 解析的结果就是老 deferred 的状态转移函数入参;

    • 下面详解 then 方法的实现
      then 方法是 promise 标准的核心方法,也是最复杂的一个,先来看代码:
    then: function( onFulfilled, onReject, onProgress ) {
    
      // 嵌套多层异步行为时的深度标识
      var maxDepth = 0;
      
      // 返回一个解析方法,通过传入的参数进行配置
      function resolve( depth, deferred, handler, special ) {
        // 核心解析方法,在回调中实际执行的方法
        return function() {
          ...
        }
      }
      
      // 返回一个新的 deferred 对象,并向其回调列表中注入解析方法
      return jQuery.Deferred( function( newDefer ) {
        
         // 可以看出,用 then 方法的三个入参来配置解析函数,并注入相应的回调列表中
         // 而且注意到,then 注入的回调是放在 tuple[ 3 ] 里
         // 而之前 done、fail、progress 注入的回调是放在 tuple[ 2 ] 里
         tuples[ 0 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onProgress ) ?
                onProgress :
                Indentity
            )
         );
    
         tuples[ 1 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onFulfilled ) ?
                onFulfilled :
                Indentity
            )
         );
    
         tuples[ 2 ][ 3 ].add(
            resolve(
              0,
              newDefer,
              isFunction( onRejected ) ?
                onRejected :
                Thrower
            )
         );
    
      } ).promise()
    }
    

    下面具体来看一下 resolve 解析方法的实现:

    function reslove( depth, deferred, handler, specail ) {
      return function() {
        
        // 调用方法时,如果传入了上下文对象,比如用 resolveWith 更改方法时
        // 就将上下文赋予 that,以便在 mightThrow 函数中调用 
        var that = this,
          args = arguments,
          
          mightThrow = function() {
            var returned, then;
            
            // 如果 depth < maxDepth,说明在 depth 这个深度的 deferred 已经被解析过了,不允许重复解析
            if ( depth < maxDepth ) {
              return;
            }
            
            returned = handler.apply( that, args );
            
            // 如果返回值是 deferred 自身的 promise,需要抛出异常
            // 否则会将 deferred 的状态转移函数注入自身的回调列表中,造成自己依赖于自己的矛盾
            if ( returned === deferred.promise() ) {
              throw new TypeError( "Thenable self-resolution" );
            }
            
            // 如果 returned 可能是一种 promise 实现,则将 returned.then 赋予 then 变量 
            then = returned &&
              ( typeof returned === "object" ||
                typeof returned === "function" ) &&
              returned.then;
            
            // 如果 then 是 function,可以确定 returned 就是一种 promise 实现
            if ( isFunction( then ) ) {
    
              // 如果有 special,说明已经指定了 onProgress 回调,无需再由 resolve 生成
              // 而且该 function 是注入到 progress 回调列表中,不影响 maxDepth,即不阻止 notify 调用
              if ( special ) {
                then.call(
                  returned,
                  resolve( maxDepth, deferred, Indentity, special ),
                  resolve( maxDepth, deferred, Thrower, special )   
                );
              } else {
                
                // 否则,该 function 必然是注入 done 回调中,最后一个参数的 special 传入了 deferred.notifyWith
                maxDepth++;
                then.call(
                  returned,
                  resolve( maxDepth, deferred, Indentity, special ),
                  resolve( maxDepth, deferred, Thrower, special ),
                  resolve( maxDepth, deferred, Indentity, deferred.notifyWith )
                );
              }
            } else {
              
              // 如果 then 不是 function,则说明 returned 就是要返回的 value 值
              // 如果 handler 不是 Indentity,那 handler 必然是自定义的回调
              // 则该异步解析函数的深度为 0,原来的 deferred 状态转移时回调执行的上下文不会影响新返回的 deferred 回调执行的上下文
              if ( handler !== Indentity ) {
                that = undefined;
                args = [ returned ];
              }
              
              // 如果 special 存在,就应该是 notifyWith 方法,直接调用即可
              // 否则 handler 执行时未抛出异常,说明状态是 fulfilled,调用 deferred.resolveWith 方法即可
              ( special || deferred.resolveWith )( that, args );
    
            }
          },
    
          // special 存在即为 notify 调用,不会抛出异常,直接调用 mightThrow 即可
          // 否则可能是 resolved 或 rejected,需要捕获异常
          process = special ? 
            mightThrow :
            function() {
              try{
                mightThrow()
              } catch( e ) {
                if ( jQuery.Deferred.exceptionHook ) {
    
                  // 在控制台中显示异常信息
                  jQuery.Deferred.exceptionHook( e, process.stackTrace )
                }
    
                // 在已经解析过的深度发生的任何异常都忽略掉
                if ( depth + 1 >= maxDepth ) {
                  if ( handler !== Thrower ) {
                    that = undefined;
                    args = [ e ];
                  }
    
                  // 说明该异步对象已经被完全解析,此时捕获的异常就是 reject 的参数
                  // 当前解析的深度 >= maxDepth
                  deferred.rejectWith( that, args );
                }
              }
            };
    
        // 如果 depth 大于 0,则直接执行 process
        if ( depth ) {
          process();
        } else {
          if ( jQuery.Deferred.getStackHook ) {
            process.strackTrace = jQuery.Deferred.getStackHook();
          }
    
          // 否则设置异步执行
          window.setTimeout( process );
        }
      };
    }
    

    关于 resolve 的深度,这里举几个例子:

    let defer = $.Deferred();
    let defer2 = $.Deferred();
    
    let newDefer = defer.then(
      function() {
        console.log( 'defer resolved' )
        return defer2;
      },
      function() {
        console.log( 'defer rejected' )
      },
      function() {
        console.log( 'defer notified ')
      }
    )
    
    // defer resolve 后,因为回调中有异步嵌套
    // 深度为 0 的异步对象 defer 解析成功
    // 等待深度为 1 的异步对象进行解析
    // 执行该歩后打印:defer is resolved
    defer.resolve();
    
    // 此时触发的异步操作深度为 0,而此时 maxDepth = 1,所以对应回调不会执行
    // 执行该歩后不会打印任何东西
    // 注意:defer 中注册的 onProgress 不执行是应为 defer.resolve() 将其回调锁住了
    // 而 newDefer 的 onProgress 不执行才是因为 depth 而被忽略了
    defer.notify();
    
    // 因为将 newDefer 的状态更变权交给了 defer2,所以会导致 newDefer 的 onProgress 回调触发
    // 打印:newDefer is notified
    defer2.notify();
    
    let defer3 = $.Deferred();
    
    // 同时,defer2 解析后入参为 defer3,也会形成异步嵌套,此时 maxDepth = 2
    // newDefer 将会等待 defer3 的状态改变
    defer2.resolve( defer3 );
    
    // 因为 defer2 已经解析过
    // 深度 depth = 1 < maxDepth,
    // 所以不会打印任何东西
    defer2.notify()
    
    // defer3 解析完成,导致 newDefer 状态变为 fulfilled
    // 打印:newDefer is resolved
    defer3.resolve()
    

    总结:

    1. then 注入回调能支持深层的异步嵌套,而 done、fail 不能,且在所属异步对象的状态解析完成后执行;
    2. then 只是确定异步的时序关系,并不保证每个异步对象最终状态是否达到,这点从语义上也能感觉到;

    下面介绍通过 jQuery.each 将 tuples 注入 promise 和 deferred 中

    直接看源码和注释就行:

    jQuery.each( tuples, function( i, tuple ) {
    
      // list 即为 done、fail、progress 注入的回调函数列表
      var list = tuple[ 2 ],
        // 状态字符串
        stateString = tuple[ 5 ];
      
      // 将回调注册方法直接赋给 done、fail、progress
      promise[ tuple[ 1 ] ] = list.add;
      
      // 对于解析状态的数组
      if ( stateString ) {
        list.add(
          
          // 更改状态值
          function() {
            state = stateString;
          },
          
          // 废弃另一个状态的回调列表(done、fail、progress)注册的回调列表
          tuples[ 3 - i ][ 2 ].disable,
    
          // 废弃另一个状态 then 注册的回调列表
          tuples[ 3 - i ][ 3 ].disable,
          
          // 锁定 progress 的回调列表
          tuples[ 0 ][ 2 ].lock,
          tuples[ 0 ][ 3 ].lock
        );
      }
    
      // 将 then 的回调列表的 fire 方法注册进 [ done, fail, progress ] 的回调列表中
      // 所以 then 注册的回调总是在 onDone、onFail、onProgress 之后触发
      list.add( tuple[ 3 ].fire );
      
      // 向 deferred 里注入状态转移方法,在这里 this 不能是自身
      deferred[ tuple[ 0 ] ] = function() {
        deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
        return this;
      };
    
      // 向 deferred 里注入带上下文的状态转移方法
      deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
    
    } );
    

    最后:本人也是刚开始学习 jQuery 源码,此文仅作为学习笔记和心得分享,关于Deferred,也还有很多不理解的地方,欢迎各位大佬指点

    相关文章

      网友评论

        本文标题:jQuery.Deferred 对象源码解析

        本文链接:https://www.haomeiwen.com/subject/bntucftx.html