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

上一篇博客分析了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方法来分析,期间对于回调对象和延迟对象的运用还会重点分析以加深理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章