Search code examples
javascriptvarlet

Using var in functions with multiple loops?


Quoting from the book JavaScript: The Definitive Guide

Unlike variables declared with let, it is legal to declare the same variable multiple times with var. And because var variables have function scope instead of block scope, it is actually common to do this kind of redeclaration. The variable i is frequently used for integer values, and especially as the index variable of for loops. In a function with multiple for loops, it is typical for each one to begin for(var i = 0; .... Because var does not scope these variables to the loop body, each of these loops is (harmlessly) re-declaring and re-initializing the same variable.

I am unable to understand what is meant in this paragraph. First I assumed the following would not have worked with second loop not iterating, because i would be function scoped:

(function foo() {
    for (let i = 0; i < 2; i++) {
        console.log(i);
    }
    for (let i = 0; i < 2; i++) {
        console.log(i);
    }
})();

but it prints

0
1
0
1

Then I assumed this would have printed 0 0 1 1 0 1, which is also not the case:

(function foo() {
    for (var i = 0; i < 2; i++) {
        console.log(i);
        for (var i = 0; i < 2; i++) {
            console.log(i);
        }
    }
})();

Can someone help me understand what is meant by

in a function of multiple for loops, var can be used harmlessly in each loop

and how it is different to let?

To me it looks like the opposite it true (which is also very confusing) where let can be used harmlessly in functions with multiple loops:

(function foo() {
    for (let i = 0; i < 2; i++) {
        console.log(i);
        for (let i = 0; i < 2; i++) {
            console.log(i);
        }
    }
})();

prints:

0
0
1
1
0
1

Solution

  • let variables are forbidden to be re-declared in the same scope, like the following:

    let foo = 10;
    let foo = 20;

    var variables can be, though:

    var foo = 10;
    var foo = 20;
    console.log('ok');

    In your first snippet, variables declared with let in the header of a for loop only exist inside the loop; they're block scoped, and aren't scoped to the outer block or function:

    (function foo() {
        for (let i = 0; i < 2; i++) {
            // console.log(i);
        }
        for (let i = 0; i < 2; i++) {
            // console.log(i);
        }
        // doesn't exist out here:
        console.log(i);
    })();

    The let is above create is in two different scopes, which is why it's not forbidden. Similarly:

    (function foo() {
        {
            let i = 10;
        }
        {
            let i = 20;
        }
        console.log('OK up until here');
        // doesn't exist out here:
        console.log(i);
    })();

    vars, in contrast, have function scope, not block scope - with your var version of the loop, since there's only one function, this:

    (function foo() {
        for (var i = 0; i < 2; i++) {
            console.log(i);
            for (var i = 0; i < 2; i++) {
                console.log(i);
            }
        }
    })();
    

    is equivalent to

    (function foo() {
        var i; // the `i` variable exists at this level
        for (i = 0; i < 2; i++) {
            console.log(i);
            for (i = 0; i < 2; i++) {
                console.log(i);
            }
        }
    })();
    

    Then I assumed this would have printed 0 0 1 1 0 1, which is also not the case:

    i gets initialized to zero at the beginning of each loop.

    The first loop begins once, at the start of the function.

    The second loop begins just after logging i, on each iteration of the outer loop.

    (function foo() {
        for (var i = 0; i < 2; i++) {
            console.log(i);
            for (var i = 0; i < 2; i++) {
                console.log(i);
            }
        }
    })();
    
    • outer loop initialization: 0 is assigned to i
    • 0 gets logged
    • inner loop initialization: 0 is assigned to i
    • 0 gets logged
    • inner loop increments i to 1 and starts again
    • inner loop: 1 gets logged
    • inner loop increments i to 2 and breaks, since i < 2 is no longer fulfilled
    • outer loop increments i to 3 and breaks, since i < 2 is no longer fulfilled

    So you get 0 0 1 logged.

    What the article probably means by

    in a function of multiple for loops, var can be used harmlessly in each loop

    is that it can work if both loops are on the same level of the function, eg

    for (...) {
    }
    for (...) {
    }
    

    It definitely won't work with nested loops, if both loops use the same variable, because there's only ever a single i variable, rather than one for each loop.

    where let can be used harmlessly in functions with multiple loops:

    For nested loops, yes, since variables declared with let are unique to each loop, rather than being shared across the whole containing function.