zoukankan      html  css  js  c++  java
  • 破解jQuery Deferred()异步执行的神秘世界

    本文解析jQuery.Deferred()源代码,学习异步执行实现原理和编程技术。

    测试代码:
    var f1=function(){
      var deferred = $.Deferred();
      deferred.resolve('f1 resolved');
      return deferred.promise();
    }
    f1().then(function(data){
      //$.when().then(function(){
        console.log(data);
        var deferred = $.Deferred();
        setTimeout(function(){
          deferred.resolve('then resolved');
        },2000);
        return deferred.promise();
    }).done(function(data){ // 也可以写.then(f1,f2,f3)
      console.log(data);
    }).fail(function(){
      console.log('fail');
    }).progress(function(){
      console.log('progress');
    });

    jQuery Deferred()源代码分为两个嵌入到jquery对象的函数块:

    jQuery.Callbacks = function( options ) {
      定义promise/deferred对象的内部操作方法以及内部数据...

    jQuery.extend({
      Deferred: function( func ) {
        定义promise/deferred对象以及暴露的方法比如resolve/then...

    Deferred()代码:

    Deferred: function( func ) {

      var tuples = [
        // action, add listener, listener list, final state
        [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], // jQuery.Callbacks调用结果是返回一个self{}对象,含内部处理函数
        [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
        [ "notify", "progress", jQuery.Callbacks("memory") ]
      ],

      state = "pending",  // 这个就是当前Deferred实例的状态

      promise = {}; //返回的实例(子实例)

      deferred = {}; //当前实例(父实例,含子实例)


      jQuery.each( tuples, function( i, tuple ) {
        var list = tuple[ 2 ], // tuples是调Callbacks获取self对象,因此list引用self对象
        // deferred[ resolve | reject | notify ]
        deferred[ tuple[0] ] = function() { // 循环三次分别构造deferred.resolve/reject/notify函数代码
          deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); //调deferred.resolveWith,构造参数,resolve函数是deferrred
                        的内置函数,this就是deferred,arguments就是调resolve时的入口参数,比如resolve(data)。此句即为:
                          deferred.resolveWith(promise,arguments);
                        传递的作用域object是promise而不是deferred。
                        resolveWith就是fireWith,因此就是调fireWith(promise.arguments),fireWith函数代码如下:       

                        // Call all callbacks with the given context and arguments
                        fireWith: function( context, args ) { // context是promise,args是调resolve传递的参数
                          fire( args );  // fire函数在jQuery.Callbacks函数中定义,下面是Callbacks函数结构
                            

                            //jQuery.Callbacks相当于定义一个类,从外部调self里面的public方法,public方法再调private方法
                            jQuery.Callbacks = function( options ) {
                              list = [],  // 执行then时把内部callback存储在list[],内部callback会调用应用callback

                              // Fire callbacks
                              fire = function( data ) {
                                list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) // 执行list[]里面的内部callback
                              }

                              

                              // Actual Callbacks object
                              self = {
                                // Call all callbacks with the given context and arguments
                                fireWith: function( context, args ) {
                                  fire( args );
                                }
                              }

                              return self; // Deferred()会调用这个函数,引用self,因此Callbacks函数执行完之后self还存在,list[]也是。  

        deferred[ tuple[0] + "With" ] = list.fireWith; // deferred.resolveWith

        //因此resolve方法其实就是调用 resolve -> resoveWith = list.fireWith (引用self) -> fire (执行list[]里面的callback),代码设计非常复杂深奥,它没有用new实例方法,而是利用了js对象引用机制,函数定义一个对象,在其它位置引用这个对象,这个对象就一直存在,直到引用它的函数不存在为止,其实用new实例方法可以简化设计不用这么深奥。

    });

    下面来看then代码,Deferrred最复杂深奥的代码在then代码,体现了作者高超的编程技术,令人叹为观止:

    then: function( /* fnDone, fnFail, fnProgress */ ) {

      var fns = arguments;
      return jQuery.Deferred(function( newDefer ) {  // then是Deferred实例里面的方法,注意这是递归再次调用Deferred创建子实例,当前有一个deferred,又递归再次执行函数新建一个deferred,至此就有两个deferred实例,也就是父子实例,子实例返回以便连写.then(callback),对于下一个.then来说就是父实例,执行下一个.then时又递归执行Deferred函数新建一个子实例返回,以此类推,连写嵌套多少层都一样。

        jQuery.each( tuples, function( i, tuple ) {
          var action = tuple[ 0 ],
          fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
          // deferred[ done | fail | progress ] for forwarding actions to newDefer
          deferred[ tuple[1] ](function() { // 循环三次就是执行:
            //deferrred.done(callback); // done是注册callback,意思是完成以后执行callback,前一个promise对象resolve时会执行callback。
            //deferrred.reject(callback); // 类似
            //deferrred.notify(callback); // 类似
            //每次都用相同的callback。

            //这是注册内部callback(匿名函数),要注册到当前deferred实例,deferred是then所在的实例,then递归调用Deferred新产生的实例也是deferred,但它是传递给回调,因此回调的newDefer是新建的子实例。奥妙就在于回调匿名函数function(newDefer){},在递归调用Deferred新建一套实例时用回调函数代码引用当前已经存在的实例!

            //当resolve时会执行内部callback匿名函数,会执行应用callback(也就是then里面的callback),然后再resolve当前promise对象,执行下一个then里面的callback。

            //为什么不直接执行应用callback是因为要处理解决当前promise对象,要判断应用callback是否返回promise对象,所以写了一段内部callback。

            var returned = fn && fn.apply( this, arguments ); //fn是从then参数获取的应用callback,执行then里面的应用callback。
            if ( returned && jQuery.isFunction( returned.promise ) ) { //下面代码的目的就是要 resolve当前then返回的promise对象从而执行下一个then里面的callback
              returned.promise()
                .done( newDefer.resolve ) //如果执行then里面的callback返回promise对象,就注册一个callback,否则就直接执行resolveWith
                .fail( newDefer.reject )
                .progress( newDefer.notify );
            } else {
              newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
              // 第一次循环时执行newDefer.resolveWith(promise,[returned]),就是直接去执行下一个then里面的callback并且传递执行应用callback返回的数据。

              //其它循环执行rejectWith/NotifyWith,类似。

            }
          });
          fns = null;
        }).promise(); //用promise()方法获取promise对象实例返回,promise对象实例只有部分方法,而deferred对象实例有所有的方法
      },

    可能有点乱有点晕,下面小结一下:

    执行then时把内部callback注册存储在list[],当resolve当前promise对象时,就是去list[]找callback执行,内部callback会执行应用callback(then里面的callback),再根据返回值resolve当前promise对象,从而执行下一个then里面的callback。

    jQuery Derferred代码比es6-promise复杂,es6-promise用父子实例,then返回的实例是子实例,then把callback存储到父实例,这样当resolve父实例时就从父实例找callback执行即可,比较简单。但jQuery defer设计了一个Callbacks函数,相当于定义了一个类,有public/private方法,有self{}和list[]。执行then时构造一个内部callback存储在当前实例(父实例)的list[],当resolve当前实例时就去list[]找内部callback执行,内部callback会执行应用callback(then里面的callback),再resolve返回的子实例去list[]执行内部callback,这就是执行下一个then()里面的callback。每一层then都要重新递归执行jQuery.Deferred(func)新建一套子实例返回,下一个then就是新建子实例的内置方法,当执行下一个then时,then所在的实例就是父实例,递归执行Deferred新建实例就是子实例,如果连写多个.then则如此层层嵌套递归。递归是很复杂的,它是把当前程序代码又执行一遍,又产生一套实例,两套代码和实例完全一样,嵌套递归多少层重复执行多少次都不出错,如何区分处理父子实例需要高超的对象编程技术。

     所以jQuery Deferred程序和前端框架编译程序一样都是递归程序,重复执行多次就产生多个实例,就递归多个子节点,每一次递归执行时的位置和环境有所变化。

    所以通用软件要解决复杂编程技术问题,而相比之下应用软件一般都不涉及复杂编程技术,是两个相当不同的编程世界。

    最后一个问题就是如何修改实例的state状态:

    state是在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",

    有一个内部callback负责修改state状态,其代码和注册位置:
        jQuery.each( tuples, function( i, tuple ) {
          stateString = tuple[ 3 ];
          // Handle state
          if ( stateString ) { // resolved或rejected
            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 ); //如果resolved就disable rejected,如果rejected就disable resolved
        }

    执行内部callback时传递promise为作用域:
    list[ firingIndex ].apply( data[ 0 ], data[ 1 ] // data[0]=promise data[1]=resolve参数

    那么执行function(){state = stateString;} 时就是修改实例中的state,传递作用域为promise有何意义?执行这个方法时是在Callbacks程序空间,如何能访问到deferred实例中的state呢?

    这个函数是在Deferred程序空间构造的,因此可以访问state,很简单,虽然把它放到list[]中,再调用执行,但函数是对象,就是引用,这个函数对象是在Deferred
    程序空间定义的,就能访问Deferred程序空间的变量数据。

    jQuery Deferred没有采用new实例方法,而是利用js对象引用机制,给阅读程序带来很大的迷惑,函数封装绕来绕去就是访问函数定义的一个对象,按程序机制,函数执行完就清除不存在了,包括局部数据都清除不存在了,但它还能在之后访问函数定义的对象。

    promise机制其实类似trigger/on机制,都是先注册保存callback,再事件触发执行callback,只是它可以连写,形式上非常直观简洁,能连写是因为它返回promise对象,返回promise对象可不简单,就要涉及递归编程技术,trigger/on之类的程序不是递归程序,所以不能连写,能连写必然是递归程序。而callback的注册保存方法各种软件各不相同,用new实例方法最简明,没有迷惑,不用new实例方法就更复杂,很迷惑,因为那么多独立定义的函数和变量,在不同的程序空间,有些看似没关系,你如何能保证随时随地从某一个函数能访问到某一个变量?

    还有一些处理细节也挺复杂的,本文不再分析。。。

    bye

  • 相关阅读:
    【python】opencv教程CV2模块——图片处理,裁剪缩放加边框
    【python】opencv教程CV2模块——画图,来左边跟我一起画星星在右边画彩虹
    【python】opencv教程CV2模块——图片处理,剪切缩放旋转
    【python】opencv教程CV2模块——批量视频截屏
    【python】opencv教程CV2模块——视频捕获,延时摄影视频、鬼畜表情包密集制作
    代码-JS之正则验证邮箱格式
    代码-JS之正则解决结巴程序
    代码-JS之IE+GOOGLE兼容函数
    代码-JS之正则replace函数
    代码-JS之下拉菜单
  • 原文地址:https://www.cnblogs.com/pzhu1/p/9106349.html
Copyright © 2011-2022 走看看