简体   繁体   English

jQuery在$ .queue()中的哪里做动画/计时器?

[英]Where does jQuery do animations/timers in `$.queue()`?

I was looking through the source of jQuery (specifically the queue() function) and saw that in just puts the function into a the .data() object associated with that element: 我查看了jQuery的源代码(特别是queue()函数),发现只是将函数放入与该元素关联的.data()对象中:

queue: function( elem, type, data ) {
    var q;
    if ( elem ) {
        type = ( type || "fx" ) + "queue";
        q = jQuery._data( elem, type );

        // Speed up dequeue by getting out quickly if this is just a lookup
        if ( data ) {
            if ( !q || jQuery.isArray(data) ) {
                q = jQuery._data( elem, type, jQuery.makeArray(data) );
            } else {
                q.push( data );
            }
        }
        return q || [];
    }
}

Now looking at the ._data which is just .data() with a fourth argument of true, where are any timers or animations being set? 现在查看只是.data()且第四个参数为true的._data ,在哪里设置了计时器或动画? Or any function calls for that matter: 或任何函数都需要这样做:

data: function( elem, name, data, pvt /* Internal Use Only */ ) {
    if ( !jQuery.acceptData( elem ) ) {
        return;
    }

    var privateCache, thisCache, ret,
        internalKey = jQuery.expando,
        getByName = typeof name === "string",

        // We have to handle DOM nodes and JS objects differently because IE6-7
        // can't GC object references properly across the DOM-JS boundary
        isNode = elem.nodeType,

        // Only DOM nodes need the global jQuery cache; JS object data is
        // attached directly to the object so GC can occur automatically
        cache = isNode ? jQuery.cache : elem,

        // Only defining an ID for JS objects if its cache already exists allows
        // the code to shortcut on the same path as a DOM node with no cache
        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
        isEvents = name === "events";

    // Avoid doing any more work than we need to when trying to get data on an
    // object that has no data at all
    if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
        return;
    }

    if ( !id ) {
        // Only DOM nodes need a new unique ID for each element since their data
        // ends up in the global cache
        if ( isNode ) {
            elem[ internalKey ] = id = ++jQuery.uuid;
        } else {
            id = internalKey;
        }
    }

    if ( !cache[ id ] ) {
        cache[ id ] = {};

        // Avoids exposing jQuery metadata on plain JS objects when the object
        // is serialized using JSON.stringify
        if ( !isNode ) {
            cache[ id ].toJSON = jQuery.noop;
        }
    }

    // An object can be passed to jQuery.data instead of a key/value pair; this gets
    // shallow copied over onto the existing cache
    if ( typeof name === "object" || typeof name === "function" ) {
        if ( pvt ) {
            cache[ id ] = jQuery.extend( cache[ id ], name );
        } else {
            cache[ id ].data = jQuery.extend( cache[ id ].data, name );
        }
    }

    privateCache = thisCache = cache[ id ];

    // jQuery data() is stored in a separate object inside the object's internal data
    // cache in order to avoid key collisions between internal data and user-defined
    // data.
    if ( !pvt ) {
        if ( !thisCache.data ) {
            thisCache.data = {};
        }

        thisCache = thisCache.data;
    }

    if ( data !== undefined ) {
        thisCache[ jQuery.camelCase( name ) ] = data;
    }

    // Users should not attempt to inspect the internal events object using jQuery.data,
    // it is undocumented and subject to change. But does anyone listen? No.
    if ( isEvents && !thisCache[ name ] ) {
        return privateCache.events;
    }

    // Check for both converted-to-camel and non-converted data property names
    // If a data property was specified
    if ( getByName ) {

        // First Try to find as-is property data
        ret = thisCache[ name ];

        // Test for null|undefined property data
        if ( ret == null ) {

            // Try to find the camelCased property
            ret = thisCache[ jQuery.camelCase( name ) ];
        }
    } else {
        ret = thisCache;
    }

    return ret;
}

EDIT for zzzzBov: 编辑zzzzBov:

animate: function( prop, speed, easing, callback ) {
    var optall = jQuery.speed( speed, easing, callback );

    if ( jQuery.isEmptyObject( prop ) ) {
        return this.each( optall.complete, [ false ] );
....
    return optall.queue === false ?
        this.each( doAnimation ) :
        this.queue( optall.queue, doAnimation );

jQuery's animate method is a complex beast ( see reference below ). jQuery的animate方法是一个复杂的野兽( 请参阅下面的参考资料 )。 It starts by normalizing the parameters, and then immediately jumps into the doAnimation function which is the callback used by jQuery's queue method . 首先通过规范化参数开始,然后立即跳转到doAnimation函数,该函数是jQuery的queue方法使用的回调。 jQuery queue's animations so they happen in order. jQuery队列的动画使它们按顺序发生。 The queue doesn't animate anything by itself, it simply acts as a trigger for the code that performs the animation. 队列本身不会设置任何动画,它只是充当执行动画的代码的触发器。

At the end of doAnimation , there is a loop to animate every relevant property in the animation ( lines 8576 - 8618, v1.7.2 ). doAnimation的末尾,有一个循环为动画中的每个相关属性设置动画( 第8576至8618行,v1.7.2 )。 The first inner-line of the loop instantiates a new jQuery.fx object: 循环的第一个内线实例化一个新的jQuery.fx对象:

e = new jQuery.fx( this, opt, p );

At the end of the loop, e.custom(...) is called in a couple places. 在循环的最后,在几个地方调用了e.custom(...) This is the important function. 这是重要的功能。 If you look through jQuery.fx.prototype.custom ( see reference below ), you'll find: 如果您浏览jQuery.fx.prototype.custom请参阅下面的参考资料 ),则会发现:

if ( t() && jQuery.timers.push(t) && !timerId ) {
    timerId = setInterval( fx.tick, fx.interval );
}

The line with setInterval is where jQuery's animation heartbeat is started. setInterval行是jQuery动画心跳开始的地方。 This points to jQuery.fx.tick ( see reference below ) which iterates through every timer in jQuery.timers . 这指向jQuery.fx.tick请参阅下面的参考资料 ),它会迭代jQuery.timers每个计时器。 If you look at the snippet above, you'll notice that part of the if statement involves pushing t into the stack of timers. 如果您看一下上面的代码片段,您会注意到if语句的一部分涉及将t推入定时器堆栈。 t was set in jQuery.fx.prototype.custom as: jQuery.fx.prototype.custom中将t设置为:

function t( gotoEnd ) {
    return self.step( gotoEnd );
}

And that right there is where jQuery's animation happens. jQuery动画就在那里发生。


Reference 参考

jQuery's animate function ( lines 8484 - 8627, v1.7.2 ): jQuery的animate功能( 第8484-8627行,v1.7.2 ):

animate: function( prop, speed, easing, callback ) {
    var optall = jQuery.speed( speed, easing, callback );

    if ( jQuery.isEmptyObject( prop ) ) {
        return this.each( optall.complete, [ false ] );
    }

    // Do not change referenced properties as per-property easing will be lost
    prop = jQuery.extend( {}, prop );

    function doAnimation() {
        // XXX 'this' does not always have a nodeName when running the
        // test suite

        if ( optall.queue === false ) {
            jQuery._mark( this );
        }

        var opt = jQuery.extend( {}, optall ),
            isElement = this.nodeType === 1,
            hidden = isElement && jQuery(this).is(":hidden"),
            name, val, p, e, hooks, replace,
            parts, start, end, unit,
            method;

        // will store per property easing and be used to determine when an animation is complete
        opt.animatedProperties = {};

        // first pass over propertys to expand / normalize
        for ( p in prop ) {
            name = jQuery.camelCase( p );
            if ( p !== name ) {
                prop[ name ] = prop[ p ];
                delete prop[ p ];
            }

            if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
                replace = hooks.expand( prop[ name ] );
                delete prop[ name ];

                // not quite $.extend, this wont overwrite keys already present.
                // also - reusing 'p' from above because we have the correct "name"
                for ( p in replace ) {
                    if ( ! ( p in prop ) ) {
                        prop[ p ] = replace[ p ];
                    }
                }
            }
        }

        for ( name in prop ) {
            val = prop[ name ];
            // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
            if ( jQuery.isArray( val ) ) {
                opt.animatedProperties[ name ] = val[ 1 ];
                val = prop[ name ] = val[ 0 ];
            } else {
                opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
            }

            if ( val === "hide" && hidden || val === "show" && !hidden ) {
                return opt.complete.call( this );
            }

            if ( isElement && ( name === "height" || name === "width" ) ) {
                // Make sure that nothing sneaks out
                // Record all 3 overflow attributes because IE does not
                // change the overflow attribute when overflowX and
                // overflowY are set to the same value
                opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];

                // Set display property to inline-block for height/width
                // animations on inline elements that are having width/height animated
                if ( jQuery.css( this, "display" ) === "inline" &&
                        jQuery.css( this, "float" ) === "none" ) {

                    // inline-level elements accept inline-block;
                    // block-level elements need to be inline with layout
                    if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
                        this.style.display = "inline-block";

                    } else {
                        this.style.zoom = 1;
                    }
                }
            }
        }

        if ( opt.overflow != null ) {
            this.style.overflow = "hidden";
        }

        for ( p in prop ) {
            e = new jQuery.fx( this, opt, p );
            val = prop[ p ];

            if ( rfxtypes.test( val ) ) {

                // Tracks whether to show or hide based on private
                // data attached to the element
                method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
                if ( method ) {
                    jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
                    e[ method ]();
                } else {
                    e[ val ]();
                }

            } else {
                parts = rfxnum.exec( val );
                start = e.cur();

                if ( parts ) {
                    end = parseFloat( parts[2] );
                    unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );

                    // We need to compute starting value
                    if ( unit !== "px" ) {
                        jQuery.style( this, p, (end || 1) + unit);
                        start = ( (end || 1) / e.cur() ) * start;
                        jQuery.style( this, p, start + unit);
                    }

                    // If a +=/-= token was provided, we're doing a relative animation
                    if ( parts[1] ) {
                        end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
                    }

                    e.custom( start, end, unit );

                } else {
                    e.custom( start, val, "" );
                }
            }
        }

        // For JS strict compliance
        return true;
    }

    return optall.queue === false ?
        this.each( doAnimation ) :
        this.queue( optall.queue, doAnimation );
}

Instantiation of jQuery.fx ( line 8577, v1.7.2 ): 实例化jQuery.fx8577行,v1.7.2 ):

e = new jQuery.fx( this, opt, p );

jQuery.fx.prototype.custom (lines 8806 - 8836, v1.7.2): jQuery.fx.prototype.custom (第8806-8836行,v1.7.2):

// Start an animation from one number to another
custom: function( from, to, unit ) {
    var self = this,
        fx = jQuery.fx;

    this.startTime = fxNow || createFxNow();
    this.end = to;
    this.now = this.start = from;
    this.pos = this.state = 0;
    this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );

    function t( gotoEnd ) {
        return self.step( gotoEnd );
    }

    t.queue = this.options.queue;
    t.elem = this.elem;
    t.saveState = function() {
        if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
            if ( self.options.hide ) {
                jQuery._data( self.elem, "fxshow" + self.prop, self.start );
            } else if ( self.options.show ) {
                jQuery._data( self.elem, "fxshow" + self.prop, self.end );
            }
        }
    };

    if ( t() && jQuery.timers.push(t) && !timerId ) {
        timerId = setInterval( fx.tick, fx.interval );
    }
}

jQuery.fx.tick ( lines 8949 - 8965, v1.7.2 ): jQuery.fx.tickjQuery.fx.tick 行,v1.7.2 ):

tick: function() {
    var timer,
        timers = jQuery.timers,
        i = 0;

    for ( ; i < timers.length; i++ ) {
        timer = timers[ i ];
        // Checks the timer has not already been removed
        if ( !timer() && timers[ i ] === timer ) {
            timers.splice( i--, 1 );
        }
    }

    if ( !timers.length ) {
        jQuery.fx.stop();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM