jquery的2.0.3版本源碼系列(7):3043行-3183行,deferred延遲對象,對異步的統一管理

目錄php

part1 deferred延遲對象jquery

part2  when輔助方法ajax

網盤源代碼 連接: https://pan.baidu.com/s/1skAj8Jj 密碼: izta數組

part1 deferred延遲對象promise

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;
                }
            }
        }

經過遍歷,對每一個延遲對象進行處理。

相關文章
相關標籤/搜索