实例化方法queue和原生方法dequeue实现了队列的管理

当实现很复杂的动画时,队列管理显得很重要

举例来说

$("#div1").animate({
    "width":400,
    "height":300
},300).animate({
    "left":600
},300);

这个动画希望先让#div1的宽变到400,高变到300,然后再让向右移动600

需要注意的是在进行第一步操作的时候高一定会先到达目的值300,但是高到达目的值的时候宽不一定到了

而且希望宽变成400,高变成300完事了以后再向右移动,而不是变宽变高的同时还移动着

先看第一个animate的调用

在animate内部,首先还是调用了queue实例化方法,传给queue的函数将作为一个队列函数存到DOM元素的queue.fx属性中

第一次存进去的时候会顺便调用这个函数,调用的过程中给这个DOM元素绑定curAnim属性

curAnim属性的值即为传进来的prop,在此处就是

{
    "width":400,
    "height":300
}

接下来prop里面有几项,就会创建几个jQuery.fx对象

在此会创建两个对象,暂且叫做widthfx对象和heightfx对象

var e = new jQuery.fx( this, jQuery.speed(speed,callback), p );

jQuery.fx的第一个参数是DOM对象,第二个参数是配置参数,第三个参数是要修改的样式的名称

其中第二个参数形如

{
    "duration":300,
    "oldComplete":undefined,
    "complete":function(){
        jQuery.dequeue(this,"fx");
    }
}

接下来执行实例化对象e的custom方法

z.custom = function(from,to){
            z.startTime = (new Date()).getTime();
            z.now = from;
            z.a();
    
            z.timer = setInterval(function(){
                z.step(from, to);
            }, 13);
        };

custom方法里面又开了一个定时器调用了step方法

但是第一次执行step也得等到13ms之后

而js不会等着,不会什么都不干就等着13ms之后执行step

定时器定义完毕之后custom方法执行完毕,继续执行animate里面的for in循环

创建heightfx对象,然后调用heightfx对象的custom方法开一个定时器

和widthfx对象一样,js也不会等着13ms之后执行step

定时器定义完毕之后custom方法执行完毕,此时animate里面的for in循环也循环完了

第一个animate就执行完了

继续执行第二个animate

和刚才的操作一样,也会调用queue方法,当执行完

this.queue[type].push( fn );

这一句时,通过分析我们可以知道这时this的queue.fx属性的值为

[
    function(){
        //此处的prop是
        //{
        //    "width":400,
        //    "height":300
        //}
        this.curAnim = prop;
        for ( var p in prop ) {
            var e = new jQuery.fx( this, jQuery.speed(speed,callback), p );
            if ( prop[p].constructor == Number )
                e.custom( e.cur(), prop[p] );
            else
                e[ prop[p] ]( prop );
        }        
    },
    function(){
        //此处的prop是
        //{
        //    "left":600
        //}
        this.curAnim = prop;
        for ( var p in prop ) {
            var e = new jQuery.fx( this, jQuery.speed(speed,callback), p );
            if ( prop[p].constructor == Number )
                e.custom( e.cur(), prop[p] );
            else
                e[ prop[p] ]( prop );
        }        
    }
]

这其实就是this(#div1)上面fx类型的队列,其中第一个函数所代表的width和height的运动正在进行

再往下是实现队列很关键的一点:

if ( this.queue[type].length == 1 )
                fn.apply(this);

这里很明显不符合if里面的条件,因此刚刚往this(#div1)的queue.fx属性中添加的队列函数并不执行

等什么时候执行呢?会等第一个队列函数的width和height完事了之后才执行

实现这个功能的原理就是函数回调,接下来一步一步分析

这个queue方法接下来就执行完了,同时这第二个animate也就执行完了

一切看起来似乎都完事了

但是不要忘了刚才我们开了两个定时器

从绑定时器那个时候开始过13ms之后,这两个定时器分别会触发

所以两个animate执行完了之后真正的"动"画才刚开始

需要注意的是两个定时器是分属两个不同的fx实例化对象的:

z.custom = function(from,to){
    z.startTime = (new Date()).getTime();
    z.now = from;
    z.a();

    z.timer= setInterval(function(){
        z.step(from, to);
    }, 13);
};

因此看起来好像是两个定时器同时走,同时执行step函数,即#div1的width和height好像在同时改变

每执行一次step函数,都会用时间戳判断是否到了目的值

没有到目的值的话就每次递增或递减

关键看到目的值的情况,准确的说是超过目的值的情况

以heightfx对象的z.timer定时器为例

首先,既然超了目的值,自然会关掉定时器

并把定时器属性z.timer置为null,再将准确的目的值赋给对应的样式

再将this(#div1)的curAnim属性里面的height子属性的值置为true

此时需要注意,当height超过目的值的时候width还不一定到目的值

因为height到300就可以了,而width得到400

所以heightfx的定时器关掉了,而widthfx的定时器还在跑

而且在接下来的for in循环中由于z.el.curAnim.height!==true的条件是成立的

所以局部变量done变成了false

于是接下来的两个if都不会执行

分析到这里再往下也就很顺了

当width也到目的值400的时候再次走这里的for in循环

局部变量的值就是true了

接下来的两个if就要执行了

第一个没什么好说的,关键看第二个if

if( done && z.o.complete && z.o.complete.constructor == Function )
    z.o.complete.apply( z.el );

z.o.complete里面有一个操作是核心

jQuery.dequeue(this, "fx");

我们直接进到这个方法内部

dequeue: function(elem,type){
        type = type || "fx";
    
        if ( elem.queue && elem.queue[type] ) {
            elem.queue[type].shift();
            var f = elem.queue[type][0];        
            if ( f ) f.apply( elem );
        }
    },

前面我们分析到#div1上的queue.fx属性是一个数组,存放的是函数队列

既然都走到这一步了,那么队列里面的第一个函数就执行完了

queue.fx队列就会把第一个函数给shift掉

再把这个队列里面现在的第一个队列函数取出来执行

以此类推

其实jQuery这样做达到了一个很好的效果,那就是避免了深层嵌套

深层嵌套的工作在jQuery内部全部完成