[removed] how to pass different object to setTimeout handlers created in a loop?

前端 未结 2 1952
春和景丽
春和景丽 2020-12-10 23:01

I\'m trying to write some JS replicating jQuery\'s fadeIn and fadeOut functions. Here\'s the code I have so far:

function fadeIn(elem, d, callback)
{

    va         


        
相关标签:
2条回答
  • 2020-12-10 23:29

    Your "another approach" is correct, this is how it's usually done.

    And as for the problem of i always being a constant, that's how closures work! You see, when you create this function that does something with i (like function() { alert(i); }), that function, as they say, 'captures', or 'binds' the variable i, so that variable i does not die after the loop is finished, but continues to live on and is still referenced from that function.

    To demonstrate this concept, consider the following code:

    var i = 5;
    var fn = function() { alert(i); };
    
    fn();  // displays "5"
    
    i = 6;
    fn();  // displays "6"
    

    When it is written in this way, the concept becomes a bit more evident, doesn't it? Since you're changing the variable in the loop, after the loop is finished the variable retains it's last value of (1+steps) - and that's exactly what your function sees when it starts executing.

    To work around this, you have to create another function that will return a function. Yes, I know, kind of mind-blowing, but bear with me. Consider the revised version of my example:

    function createFn( theArgument )
    {
        return function() { alert( theArgument ); };
    }
    
    var i = 5;
    var fn = createFn( i );
    
    fn();  // displays "5"
    
    i = 6;
    fn();  // still displays "5". Voila!
    

    This works, because the fn function no longer binds the variable i. Instead, now it binds another variable - theArgument, which has nothing to do with i, other than they have the same value at the moment of calling createFn. Now you can change your i all you want - theArgument will be invincible.

    Applying this to your code, here's how you should modify it:

    function createTimeoutHandler( elemArg, iDivStepsArg )
    {
        return function() { setOpacity( elemArg, iDivStepsArg ); };
    }
    
    for (var i = 1; i <= steps; i++)
    {
        console.log(i/steps + ', ' + (i/steps) * duration);
        setTimeout( createTimeoutHandler( elem, i/steps ), (i/steps) * duration);
    }
    
    0 讨论(0)
  • 2020-12-10 23:37

    Your first approach is evaluating code at runtime. You are most likely right about why it's failing (elem is not in the scope in which the code is eval'd). Using any form of eval() (and setTimeout(string, ...) is a form of eval()) is a general bad idea in Javascript, it's much better to create a function as in your second approach.

    To understand why your second approach is failing you need to understand scopes and specifically closures. When you create that function, it grabs a reference to the i variable from the fadeIn function's scope.

    When you later run the function, it uses that reference to refer back to the i from fadeIn's scope. By the time this happens however, the loop is over so you'll forever just get i being whatever it was when that loop ended.

    What you should do is re-engineer it so that instead of creating many setTimeouts at once (which is inefficient) you instead tell your setTimeout callback function to set the next Timeout (or you could use setInterval) and do the incrementing if your values inside that callback function.

    0 讨论(0)
提交回复
热议问题