1

我正在查看 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 || [];
    }
}

现在看看._datawhich is 只是.data()第四个参数 true,在哪里设置了计时器或动画?或者任何函数调用这个问题:

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

编辑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 );
4

1 回答 1

3

jQuery 的animate方法是一个复杂的野兽(参见下面的参考资料)。它首先对参数进行规范化,然后立即跳转到jQuery方法doAnimation使用的回调函数。jQuery 队列的动画,因此它们按顺序发生。队列本身不会为任何东西设置动画,它只是充当执行动画的代码的触发器。queue

在结尾处doAnimation,有一个循环来为动画中的每个相关属性设置动画(第 8576 - 8618 行,v1.7.2 行)。循环的第一行实例化一个新jQuery.fx对象:

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

在循环结束时,e.custom(...)在几个地方调用。这是重要的功能。如果您仔细查看jQuery.fx.prototype.custom请参阅下面的参考资料),您会发现:

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

setInterval是 jQuery 的动画心跳开始的地方。这指向jQuery.fx.tick参见下面的参考资料),它遍历每个计时器jQuery.timers。如果您查看上面的代码片段,您会注意到该if语句的一部分涉及推t入计时器堆栈。t设置jQuery.fx.prototype.custom为:

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

这就是 jQuery 动画发生的地方。


参考

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

jQuery.fx第 8577 行,v1.7.2 )的实例化:

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

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第 8949 - 8965 行,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();
    }
}
于 2012-06-05T00:50:35.143 回答