jQuery源码阅读(十三)---jQuery异步队列模块

来源:互联网 发布:淘宝推广佣金 编辑:程序博客网 时间:2024/05/18 03:28

上一篇博客分析了Callbacks实现原理,而在jQuery中,Ready函数的实现,异步队列以及队列模块都用到了Callbacks对象。jQuery.ready函数在前面已经做了整理,所以这篇博客主要是分析Deffered(异步队列)和jQuery中异步队列的应用。

异步队列

延迟对象(异步队列)是在回调对象的基础上实现的。这个延迟对象维护了三个列表:成功(done)回调函数列表,失败(fail)回调函数列表和进行中(progress)回调函数列表,之所以有三个回调函数列表,是因为延迟对象有三种状态,分别是:resolve(成功), reject(失败), notify(进行中)。
那么延迟对象是如何在运用回调对象模块来实现异步功能的?

看一个例子:

var dfd = $.Deferred();     //与回调对象类似,先定义一个延迟对象;setTimeout( function(){   //当时间满足一秒时    dfd.resolve();      //相当于$.Callbacks().fire()方法,将加入到done(成功回调列表)中的函数执行,在这里将弹出有'Yes'的窗口}, 1000);dfd.done(function(){    //相当于$.Callbacks().add(function(){})方法//不同的是,这里分别通过done和fail方法,将不同的回调函数添加到各自的回调列表中。    alert('Yes');}).fail(function(){      alert('No');       //如果setTimeOut中,将dfd.resolve()换成dfd.reject(),将弹出有'No'的窗口})

根据上面分析,我们可以得到这样一个对应关系:
这里写图片描述

下来我们看看源码中是如何实现的?

源码框架

jQuery.extend({    Deferred: function( func ){        //三个回调函数列表        doneList = $.Callbacks('once memory')        failList = $.Callbacks('once memory')        progressList = $.Callbacks('memory')        //初始状态        state = 'pending'  //表示进行中        //三种状态和三个回调函数列表对应        list = {            resolve: doneList,            reject: failList,            notify: progressList        }        promise = {            done: doneList.add,            fail: failList.add,            progress: progressList.add,            state: function(){  },            then: function(){  },    //依次添加doneList,failList和progressList回调函数            always: function(){  },  //不管触发resolve函数还是reject函数,都会执行该方法添加的回调函数            pipe: function(){  },               promise: function(){  }  //返回promise对象的一个副本或者扩展的延迟对象        }        //重新建一个promise的副本        deferred = promise.promise({});        //给deferred延迟对象添加三种状态函数        for(key in list)        {            deferred[key] = list[key].fire;            deferred[key + 'With'] = list[key].fireWith;        }        //执行deferred.resolve或者deferred.reject时,要改变初始的状态,并且一旦状态确定,便不会再更改,所以有下面的操作        deferred.done(function(){            state = 'resolved'        }, failList.disable, progressList.lock).fail(function(){            state = 'rejected'        }, doneList.disable, progressList.lock);        if( func ) { //Deferred也是支持参数的            func.call(deferred, deferred);        }        return deferred;     //返回延迟对象    }})

在源码中,我们可以清楚的看到,延迟对象是如何利用回调对象来实现回调函数的添加和执行的。除了这些,可以看到,在Deferred函数中,创建了一个promise对象,另外还又得到了一个deferred对象,这两个对象之间有什么区别和联系呢?

这里写图片描述

可以看到,deferred对象由promise对象扩展而来,但是又比promise对象多了三个函数,这三个函数都是用于改变延迟对象状态的。那么为什么要用两个延迟对象? 这是为了保证外部不能对延迟对象的状态进行改变。
举个例子:

//函数a返回一个延迟对象function a(){    var dfd = $.Deferred();    setTimeout( function(){        dfd.resolve();    }, 1000);    return dfd;}//将a得到的延迟对象 赋值给一个新的延迟对象var dfdNew = a().done(function(){    alert("Yes");}).fail(function(){    alert('No');});//造成在函数外部,对延迟对象的状态进行了改变。dfdNew.reject();

由于上面的a函数返回的是一个延迟对象deferred,所有包含有resolve,reject和notify方法,因此可以在函数外部去改变延迟对象状态,而此时我们是不希望外部对延迟对象的状态进行改变,因此这才利用到promise对象。看下面段代码:

function a(){    var dfd = $.Deferred();    setTimeout( function(){        dfd.resolve();    }, 1000);    return dfd.promise();}var dfdNew = a().done(function(){    alert("Yes");}).fail(function(){    alert('No');});//此时得到dfdNew延迟对象,并没有resolve等改变状态的方法,因此下面的语句会出错。dfdNew.reject();

这段代码就按照我们的意愿来执行了,外部并不能对延迟对象的状态进行更改,只能通过a函数里面的dfd.resolve来改变。

异步队列的应用

在jQuery中,主要有两个部分用到了异步队列(延迟对象)模块:

$.ajax$().ready()方法。

在ready函数理解这篇博客中,整理了jQuery的ready方法,但当时还没有学习关于回调对象,延迟对象的模块,所以有一部分没有深入,下来我们再来缕一缕ready函数。
主要看readyList.add(fn)这部分代码,readyList是一个回调对象,$.Callbacks('once memory'),这就相当于Deferred对象中,成功回调函数列表、失败回调函数列表,在jQuery.fn.ready函数中,将fn回调函数添加进回调列表中。
在页面元素加载出来之后,触发之前注册的事件处理函数,这个事件处理函数中会去调jQuery.ready()方法,在这个方法中调readyList.fireWith()方法,从而实现回调函数的执行。

ajax模块暂时还没有看,所以后期整理时再回过头来分析对于Deferred延迟对象的应用。

when方法

jQuery扩展了一个when方法,这个方法相当于是对Deferred对象的一个延伸,相当于对多个延迟对象组合起来进行处理。看下面一个例子:

function a(){    var d = $.Deferred();    d.resolve();    return d;}function b(){    var d = $.Deferred();    d.resolve();    return d;}$.when(a(), b()).done(function(){    //当a()和b()同时返回一个resolve状态的延迟对象时,才会触发doneList回调列表中的回调函数    alert("Yes");}).fail(function(){    //当a()或者b()任意一个返回reject状态的延迟对象,就会触发failList列表中的回调函数     alert("No");})//而当参数不是Deferred对象或者无参数时,始终触发doneList里面的回调函数;并且可以通过arguments类数组访问到传的参数。$.when(123, 8945).done(function(){    alert("成功");}).fail(function(){    alert("失败");})

有上面分析和测试,可以想到,$.when()$.when(123, 456)是相同的处理;只有在参数为延迟对象时,才会判断这些延迟对象的状态,从而决定$.when后面添加的回调方法执行哪一个。

下来根据源码缕一遍:

function( firstParam ) {    //首先将参数转换成数组,调用[].slice方法    var args = sliceDeferred.call( arguments, 0 ),    i = 0,    length = args.length,   //得到参数的个数    pValues = new Array( length ),      count = length,    pCount = length,    //根据参数个数,以及参数类型,设置deferred对象    //当无参数或者多于一个参数时,deferred = $.Deferred()对象;    //当且仅当参数是一个延迟对象类型时,deferred = 这个延迟对象    deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(),    //deferred对象的一份拷贝,供外界使用,所以是利用promise方法得到,没有resolve(),reject()以及notify()方法,以为外界不允许对状态再进行改变    promise = deferred.promise();    //下面这两个函数先不看    function resolveFunc( i ) {    }    function progressFunc( i ) {    }    //下面利用参数个数分情况处理    if ( length > 1 ) {        //多个参数时,又分当前参数是否为延迟对象        for ( ; i < length; i++ ) {            //是延迟对象,分别向doneList列表,failList列表和progressList列表中添加回调。            //因为是延迟对象,即有'memory'标志的回调对象,如果之前延迟对象的状态已经确定,此时添加回调函数之后会立即执行回调函数。            //并且可以看到,添加到failList中的回调函数是deferred.reject,也就是说只要参数中有一个延迟对象的状态是reject,都会触发最终deferred对象的reject。            //而向doneList和progressList中添加的回调函数分别是resolveFunc和progressFunc,这两个函数后面再分析。            if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {                args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );            } else {                //如果参数不是延迟对象,count减1.                //这里说明一下count的含义。when整体的实现思路,是用一个计数器来记录是否所有的延迟对象都resolve或者都notify了。                //初始值为所有的参数个数,当参数不是延迟对象时,减一,当参数是延迟对象,且一直resolve或者一直notify,也减一。直到减为0时,触发deferred.resolve或者deferred.notify。                --count;            }        }        if ( !count ) {            //count减为0,触发deferred.resolve().            deferred.resolveWith( deferred, args );        }    } else if ( deferred !== firstParam ) {        //无参数时,触发deferred.resolveWith()        //一个参数时,且参数不为延迟对象时,同样触发deferred.resolveWith().        deferred.resolveWith( deferred, length ? [ firstParam ] : [] );    }    //一个参数,且参数为延迟对象时,deferred = 这个延迟对象。    //最后返回deferred.promise对象,也就是说,这个延迟对象的状态是什么,就去触发$.when后面添加的回调函数即可。类似于一个延迟对象的处理。    return promise;}

下面来说resolveFuncprogressFunc这两个方法:

//这两个方法分别是添加进doneList和 progressList回调列表中的回调函数function resolveFunc( i ) {    //返回一个函数,这个函数每一次对计数器count减一,并且判断在count为0时,直接触发deferred.resolve。    return function( value ) {        args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;        if ( !( --count ) ) {            deferred.resolveWith( deferred, args );        }    };}//progressFunc与上面的函数是类似的,不同的是,这是对于progressList添加的回调函数//更重要的不同点是: 只要有一个延迟对象的状态为notify,就都会触发deferred.notify。function progressFunc( i ) {    return function( value ) {        pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;        deferred.notifyWith( promise, pValues );    };}

到这里,关于回调对象,延迟对象以及延迟对象的扩展when都已经分析完了。下来为了加深对于这两个对象的理解和运用,后面可能会从jQuery功能模块的ajax方法来分析,期间对于回调对象和延迟对象的运用还会重点分析以加深理解。

原创粉丝点击