Variable definition rules for initialized variables in a for loop

Possible duplicate:
Closing Javascript inside loops - simple practical example

I am playing with setTimeout in my project to throttle the addition of elements to the DOM (so the UI will not get stuck on page load). However, I ran into something a bit puzzling to me. Given this code:

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function() {
        console.log("in timeout i is: " + i + " j is: " + j);
    }, i * 1000);
}

      

I am getting the following output:

i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14

      

That the value i

in the timeout is 5 is obvious, since I covered in the initialization of the loop. However, how come that j

equals 14 for all timeout outputs? I would think it j

would have an output of 10, 11, 12, 13, 14 on timeout since it is within the scope of the loop. How can I achieve this result?

+3


source to share


1 answer


This is because JavaScript var

has a function scope .

var

the declarations will be hoisted to the top of the current execution context. That is, if it is inside a function, it var

will be scoped inside the function execution context, otherwise the program execution context (global).

ECMAScript 2015 (aka ES6) introduces let

which allows you to create block scope sizes, but since it is not widely supported, Just leave a link for reference.

A workaround to still use var

and have "scope" within the loop is to create a new execution context, also known as a closure :

function callbackFactory(i, j) {
    // Now `i` and `j` are scoped inside each `callbackFactory` execution context.
    return function() { // This returned function will be used by the `setTimeout`.
       // Lexical scope (scope chain) will seek up the closest `i` and `j` in parent
       // scopes, that being of the `callbackFactory` scope in which this returned
       // function has been initialized.
       console.log("in timeout i is: " + i + " j is: " + j);
    };
}
for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout( callbackFactory(i, j), i * 1000);
}

      

While I have used scopes i

and j

inside the callback scopes, they will return the same values ​​internally setTimeout

as they were when they were passed to callbackFactory

.

Watch Live Demo .

Another way to do the same is to create an IIFE inside a loop for

. This is usually easier to read, but JS (H | L) int will yell at you. ;)

This is because creating functions inside a loop is considered bad for performance.

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    (function(i, j) { // new execution context created for each iteration
        setTimeout(function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        }, i * 1000);
    }(i, j)); // the variables inside the `for` are passed to the IIFE
}

      

Above, I created a new execution context internally for

on each iteration. ( Demo )



By mixing the first approach ( callbackFactory

) with the IIFE above, we could even do a third option:

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function(i, j) {
        return function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        };
    }(i, j), i * 1000);
}

      

It's just using an IIFE instead of a function callbackFactory

. This doesn't seem very easy to read and still creates functions inside the loop for

, which is bad for performance, but just notices that this is also possible and works .

These 3 approaches are very common in the wild. =]


Oh, I almost forgot to answer the main question. Just place callbackFactory

in the same scope as in the loop for

, and then instead of the scope i

inside that object, let the search scope chain look for the i

outside scope:

(function() {
    var i, j;
    function callbackFactory(j) {
    // the `j` inside this execution context enters it as a formal parameter,
    // shadowing the outer `j`. That is, it is independent from the outer `j`.
    // You could name the parameter as "k" and use "k" when logging, for example.
        return function() {
           // Scope chain will seek the closest `j` in parent scopes, that being
           // the one from the callbackFactory scope in which this returned
           // function has been initialized.
           // It will also seek up the "closest" `i`,
           // which is scoped inside the outer wrapper IIFE.
           console.log("in timeout i is: " + i + " j is: " + j);
        };
    }
    for(i = 0; i < 5; i++) {
        j = i + 10;
        console.log("i is: " + i + " j is: " + j);
        setTimeout( callbackFactory(j), i * 1000);
    }
}());
/* Yields:
i is: 0 j is: 10  
i is: 1 j is: 11  
i is: 2 j is: 12  
i is: 3 j is: 13  
i is: 4 j is: 14  
in timeout i is: 5 j is: 10  
in timeout i is: 5 j is: 11  
in timeout i is: 5 j is: 12  
in timeout i is: 5 j is: 13  
in timeout i is: 5 j is: 14 */

      

Fiddle

Please note that I've moved announcement i

and j

in the upper part of the field for better readability. It has the same effect as for (var i = [...]

being picked up by the interpreter.

+7


source







All Articles