zoukankan      html  css  js  c++  java
  • jquery的2.0.3版本源码系列(7):3043行-3183行,deferred延迟对象,对异步的统一管理

    目录

    part1 deferred延迟对象

    part2  when辅助方法

    网盘源代码 链接: https://pan.baidu.com/s/1skAj8Jj 密码: izta

    part1 deferred延迟对象

    1 . deferred基于callbacks开发

    使用callbacks完成异步:

    <script src="js/jquery-2.0.3.js"></script>
        <script>
            var cb=$.Callbacks();
            setTimeout(function(){
                alert(111);
                cb.fire();
            },1000);
            cb.add(function(){
                alert(222);
            })
            //通过回调,先弹出111,再弹出222.
        </script>

    使用deferred完成异步:

    <script src="js/jquery-2.0.3.js"></script>
       <script>
           var dfd=$.Deferred();
           setTimeout(function(){
               alert(111);
               dfd.resolve();
           })
           dfd.done(function(){
               alert(222);
           })
           //通过延迟对象,先弹出111,再弹出222.
       </script>

    我们看到,两者代码非常相似,其实deferred延迟对象本就是基于callbacks来开发的。deferred的resolve对应callbacks的fire,deferred的done对应callbacks的add。

    延迟对象除了resolve状态,还有很多其他一些状态。

    //3046行,这里定义了3套状态
    //resolve代表成功,reject代表失败,notify代表通知与过程
    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") ]
                ],

    类似:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        var dfd=$.Deferred();
        setTimeout(function(){
            alert(111);
            dfd.reject();
        })
        dfd.fail(function(){
            alert(222);
        })
        //延迟失败,调用fail方法,那么最终的结果是先弹出111,再弹出222.
    </script>

    ajax内置了deferred对象哦。

     <script src="js/jquery-2.0.3.js"></script>
         <script>
             $.ajax("xxx.php").done(function(){
                 alert("成功!")
             }).fail(function(){
                 alert("失败");
             })
         </script>

    2.延迟对象的状态映射

    我们知道deferred的成功状态包含resolve和done方法,失败状态包含reject和fail方法,进度中包含notify和progress方法。那么在源码中是如何映射起来的呢。

    1.状态映射

    //把相同状态的方法名都放到同一个数组里
    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") ]
                ], 
    //遍历数组,得到的元素也是一个数组,赋值给list
            jQuery.each( tuples, function( i, tuple ) {
                var list = tuple[ 2 ],
                    stateString = tuple[ 3 ];
                            //状态字符串,即resolved和rejected。
    
                // promise[ done | fail | progress ] = list.add
                promise[ tuple[1] ] = list.add;
                            //把done、fail添加到add方法里
    
                // 处理状态字符串
                if ( stateString ) {
                            //
                    list.add(function() {
                        // 状态字符串,即resolved和rejected
                        state = stateString;
    
                    // 一旦发生某个状态,那么对应的fire就会被禁用,或者progress就会上锁。
                    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
                }
    
                // deferred[ resolve | reject | notify ]使用了callbacks的fire触发
                deferred[ tuple[0] ] = function() {
                    deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
                    return this;
                };
                deferred[ tuple[0] + "With" ] = list.fireWith;
            });

    2.once和memory功能

    先来看callbacks。

    <body>
       <button id="btn">按钮</button>
    <script src="js/jquery-2.0.3.js"></script>
    <script>
      var cb=$.Callbacks("memory");
      cb.add(function(){
          alert("aaa");
      });
      cb.fire();
      $("#btn").click(function(){
          alert("bbb");
      })
        //当我们点击按钮时,因为memory的记忆功能,点击按钮依然能弹出bbb。
    </script>
    
    </body>

    那么类似的,deferred也有memory功能,参照3048行的代码, jQuery.Callbacks("once memory") 。我们使用progress/notify一组来测试。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        var dfd=$.Deferred();
        setInterval(function(){
            alert(111);
            dfd.notify();
        },1000);
        dfd.done(function(){
            alert("成功");
        }).fail(function(){
            alert("失败")
        }).progress(function(){
            alert("进度中")
        });
        //每隔一秒就会执行alert 111的任务,同时由于deferred的记忆功能,所以也会反复弹出"进度中"。
    </script>

    除了memory,那么once的意思是只有一次。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        var dfd=$.Deferred();
        setInterval(function(){
            alert(111);
            dfd.resolve();
        },1000);
        dfd.done(function(){
            alert("成功");
        }).fail(function(){
            alert("失败")
        }).progress(function(){
            alert("进度中")
        });
        //无论resolve每隔一秒执行多少次,最终的done方法也只执行一次。
    </script>

    3.promise对象

    1.promise对象与deferred对象的关系

    在3095行开始的 jQuery.each( tuples, function( i, tuple ) { each方法里,会把 promise[ done | fail | progress ] = list.add done、fail、progress方法添加到callbacks的add方法里,这里使用了promise对象。3113行的 deferred[ tuple[0] ] = function() { 则使用了deferred对象。那么两者之间的关系是什么呢?

    promise: function( obj ) {
        return obj != null ? jQuery.extend( obj, promise ) : promise;
                    }
    //...
    promise.promise( deferred );
      

    我们从这些源码里可以看到,promise会继承给deferred对象。

    promise = {
                    state: function() {
                        return state;
                    },
    //.......

    promise定义的方法包括state、always、then、promise、pipe、done、fail、progress等等,deferred定义的包括resolve、reject、notify方法,那么通过继承,deferred也就可以拥有所有的方法了。那么区别就是多出来的resolve、reject、notify方法。

    这里有一个应用:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            setTimeout(function(){
                dfd.resolve();
            },1000);
            return dfd;
        }
        var dfd=aaa();
        dfd.done(function(){
            alert("成功");
        }).fail(function(){
            alert("失败");
        });
        dfd.reject();
        //由于reject的修改,那么弹出失败
    </script>

    可是如果我们返回的是promise对象,那么是没有暴露resolve或者reject等方法的,所以也就不存在可以修改状态了。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            setTimeout(function(){
                dfd.resolve();
            },1000);
            return dfd.promise();
        }
        var dfd=aaa();
        dfd.done(function(){
            alert("成功");
        }).fail(function(){
            alert("失败");
        });
        dfd.reject();//报错
        //弹出成功字符串
    
    </script>

    4.state状态的控制

    state = "pending",
    //一开始state就是pending“进行中”
                promise = {
                    state: function() {
                        return state;
                    },
    jQuery.each( tuples, function( i, tuple ) {
                var list = tuple[ 2 ],
                    stateString = tuple[ 3 ];
    
                // promise[ done | fail | progress ] = list.add
                promise[ tuple[1] ] = list.add;
    
                // Handle state
                if ( stateString ) {
                    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 );
                }

    1.首先,来演示state的各个状态。

    <script src="js/jquery-2.0.3.js"></script>
      <script>
          function aaa(){
              var dfd=$.Deferred();
              alert(dfd.state());
              //首先弹出deferred对象的状态
              setTimeout(function(){
                  dfd.resolve();
                  alert(dfd.state());
              //隔一秒之后已经成功了,那么弹出状态resolved
              },1000);
              return dfd.promise();
          }
          var dfd=aaa();
          dfd.done(function(){
              alert("成功");
          }).fail(function(){
              alert("失败");
          });
      </script>

    结合deferred对象后面的代码,整个执行结果是先弹出pending,然后弹出字符串“成功”,最后弹出resolved。为什么使用的是state方法,其实源代码里揭示的十分清楚,它是一个function。

    state: function() {//3054行

    2.接下来看源代码是如何控制状态的改变的。

    var tuples = [
                
                    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                    [ "notify", "progress", jQuery.Callbacks("memory") ]
                ],

    我们看到,只有resolve和reject才有更新的状态哦,所以对于notify状态来说是没有stateString的,所以也就不会走if语句。

    jQuery.each( tuples, function( i, tuple ) {
                var list = tuple[ 2 ],
                    stateString = tuple[ 3 ];
    //第4个元素正好是状态字符串
    
                promise[ tuple[1] ] = list.add;
    
                // 处理状态字符串
                if ( stateString ) {
    //...
                    }

    3.最后看重要的if语句是如何控制的。

    if ( stateString ) {
                    list.add(function() {
                        // 将[ resolved | rejected ]赋值给state
                        state = stateString;
                    }, tuples[ i ^ 1 ][ 2 ].disable, 
                                    //因为tuple第3个元素就是回调jQuery.callbacks,那么禁用掉
                                   tuples[ 2 ][ 2 ].lock );
                                   //第3个元素也就是关于notify的,
                         //那么再数第3个元素也是回调jQuery.callbacks,锁住,就不会触发了
                }

    可以看到通过disable来禁用resolve和reject的回调,以及锁住notify的状态,那么状态一旦发生就不会改变了。

    4.补充知识:

    位运算符 tuples[ i ^ 1 ][ 2 ] 。

    jQuery.each( tuples, function( i, tuple ) {
    //3095行,说明i取值为0和1
    <script>
           alert(1^1);//弹出0
           alert(0^1);//弹出1
       </script>

    那么也就是说如果i为0,那么取值为1,如果i为1,那么取值为0。所以对于禁用的原则是遍历到谁,那么其他的禁用(有点取反的意思在哦)。

    4.deferred对象的工具方法always()、then()

     1.always方法

    always: function() {
                        deferred.done( arguments ).fail( arguments );
                        return this;
                    },
    //源码对deferred对象,done方法、fail方法都调用传入的回调

    那么看一个例子,就非常清晰了。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            setTimeout(function(){
                dfd.reject();
            },1000);
            return dfd.promise();
        }
        var dfd=aaa();
        dfd.always(function(){
            alert("无论状态如何,调用这个回调函数");
        });
    </script>

    2.then方法

    3.promise方法

     part2  when方法

    1.when的使用

    when方法在源码里是一个辅助方法。它的使用和deferred延迟对象是有区别的,deferred只能针对一个延迟对象进行操作,when能针对多个延迟对象进行判断。

    1.when方法的延迟对象同时成功时触发done方法。

    DEMO演示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        function bbb(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        $.when(aaa(),bbb()).done(function(){
            alert("成功!");
        })
    </script>

    浏览器显示结果:

    2.when方法只要有一个延迟对象的状态是失败,那么就触发fail函数。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        function bbb(){
            var dfd=$.Deferred();
            dfd.reject();
            return dfd;
        }
        $.when(aaa(),bbb()).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
    </script>

    浏览器显示结果:

    3.参数处理

    无参、基本类型(比如123)、普通的函数并没有返回promise,那么就直接跳过参数。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        $.when(123).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //基本数据类型123就直接跳过啦,那么弹出成功字符串
    </script>

     那么既然一定要一个满足返回promise的函数,为什么要这样做呢,好处是能够传参。

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        $.when(123).done(function(){
            alert(arguments[0]);
            alert("成功!");
        }).fail(function(){
            alert("失败");
        });
        //弹出123字符串和成功字符串。
    </script>

    2.when方法的源码设计思想

    我们先来假设一个例子,它有4个延迟对象,那么返回的是 return deferred.promise();//3181行 ,所以promise可以使用done方法。那么具体怎么做的呢。

    $.when(aaa(),bbb(),ccc(),ddd()).done(function(){
            alert("成功!");
        });

    先看一个图示。

    3.when()无参的情况

    DEMO演示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        $.when().done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //因为无参,那么跳过,最终弹出成功字符串。
    </script>

    进入源码,3134行,

    var i = 0,
                resolveValues = core_slice.call( arguments ),
    //这里处理的是,往when方法传基本数据的方式
                length = resolveValues.length,
    //length就是传入的参数个数,那么无参就是0咯
            
                remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    //由于length取0,那么说参数没有那么就取length也就是0.所以remaining计数器为0

    计数器remaining为0,length为0.

    deferred = remaining === 1 ? subordinate : jQuery.Deferred(),es
    //也就不用执行咯
            if ( length > 1 ) {
            }
    //if语句进不去
            if ( !remaining ) {
                deferred.resolveWith( resolveContexts, resolveValues );
            }
    //remaining为0,进入if语句,所以deferred就被resolve了
            return deferred.promise();

    既然deferred被resolve了,那么关于 $.when().done(function(){ 就可以被执行了。

    总之,如果无参,按照resolve来考虑。

    4.when()的参数是基本类型,比如123

    DEMO演示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        $.when(123).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //基本数据类型123就直接跳过啦,那么弹出成功字符串
    </script>

    进入源码,那么length为1,

    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    //有参数,而且判断非promise函数,所以remaining计数器取0

    接下来的updateFunc、length>1的if语句都不会走。!remaining的if语句会进去resolve。

    if ( !remaining ) {
                deferred.resolveWith( resolveContexts, resolveValues );
            }
    
            return deferred.promise();

    5.when方法的普通函数的参数

    DEMO展示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            dfd.resolve();
            //即使定义了延迟对象,可是并没有返回promise对象
        }
        $.when(aaa()).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //普通函数就直接跳过
    </script>

    进入源码,那么length为1,

    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    //有参数,而且判断非promise函数,所以remaining计数器取0

    接下来的updateFunc、length>1的if语句都不会走。!remaining的if语句会进去resolve。

    if ( !remaining ) {
                deferred.resolveWith( resolveContexts, resolveValues );
            }
    
            return deferred.promise();

    4.when方法只返回一个promise

    DEMO展示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        $.when(aaa()).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //弹出成功字符串!
    </script>

    进入源码,length为1,remaining计数器为1.

    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    //有参数,而且jQuery能够判断为返回promise的function,那么最终计数器为1
    //这是主要的deferred
                deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
    //计数器既然为1,那么deferred为参数本身咯。

    那么接下来的length>1的if语句就不会进入了。!remaining的if语句也不会进入。这个时候返回

    return deferred.promise();
    //自然是参数所代表的promise

    4.when方法的复杂参数的处理

    DEMO展示:

    <script src="js/jquery-2.0.3.js"></script>
    <script>
        function aaa(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        function bbb(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        function ccc(){
            var dfd=$.Deferred();
            dfd.resolve();
            return dfd;
        }
        $.when(aaa(),bbb(),ccc()).done(function(){
            alert("成功!");
        }).fail(function(){
            alert("失败");
        })
        //弹出成功字符串!
    </script>

    针对Demo所示的参数,length为3,remaining算下来就是length长度3.

    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    //既然length不等于1,逻辑运算的结果为1,那么||运算符就不用看了
    //所以结果取length
    deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
    //结果是jQuery.Deferred

    if语句就要进入了。

    // add listeners to Deferred subordinates; treat others as resolved
            if ( length > 1 ) {
                progressValues = new Array( length );
                progressContexts = new Array( length );
                resolveContexts = new Array( length );
    //length为多少,创建的数组就为多少
    //根据length遍历
                for ( ; i < length; i++ ) {
                    if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
    //遍历如果正确的参数,那么进入if语句
    
    //与此同时else是把既有正常的参数,也有基本类型。
    //那么就把非promise的情况计数器-1
                        resolveValues[ i ].promise()
                            .done( updateFunc( i, resolveContexts, resolveValues ) )
                            .fail( deferred.reject )
                            .progress( updateFunc( i, progressContexts, progressValues ) );
    //对每个参数进行处理,可以看到done方法是所有的func都要成立
    //而一旦某个fail了,就会导致master主deferred失败
    //progress是所有的func的状态变化
                    } else {
                        --remaining;
                    }
                }
            }

    通过遍历,对每个延迟对象进行处理。

  • 相关阅读:
    周总结3
    周总结6
    Java时间日期格式转换
    [专贴]在使用了母版页的内容页后,如何在javascript中调用服务器控件值
    用到函数的题目
    javascript解析dom(2)
    javascript解析dom
    自己的分页
    javascript解析DOM(3)
    转载 ajax XML dataset
  • 原文地址:https://www.cnblogs.com/chenmeng2062/p/7641524.html
Copyright © 2011-2022 走看看