Explanation of `let` and block scoping with for loops












25















I understand that let prevents duplicate declarations which is nice.



let x;
let x; // error!


Variables declared with let can also be used in closures which can be expected



let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms


What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:



// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }


Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?



I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.










share|improve this question




















  • 2





    You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

    – loganfsmyth
    Jun 17 '15 at 18:40













  • possible duplicate of Javascript - "let" keyword vs "var" keyword

    – user663031
    Jun 17 '15 at 19:11











  • See also stackoverflow.com/questions/762011/….

    – user663031
    Jun 17 '15 at 19:11











  • Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

    – Anton Temchenko
    Jun 13 '16 at 17:49











  • Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

    – Marek
    Nov 19 '17 at 1:02


















25















I understand that let prevents duplicate declarations which is nice.



let x;
let x; // error!


Variables declared with let can also be used in closures which can be expected



let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms


What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:



// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }


Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?



I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.










share|improve this question




















  • 2





    You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

    – loganfsmyth
    Jun 17 '15 at 18:40













  • possible duplicate of Javascript - "let" keyword vs "var" keyword

    – user663031
    Jun 17 '15 at 19:11











  • See also stackoverflow.com/questions/762011/….

    – user663031
    Jun 17 '15 at 19:11











  • Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

    – Anton Temchenko
    Jun 13 '16 at 17:49











  • Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

    – Marek
    Nov 19 '17 at 1:02
















25












25








25


12






I understand that let prevents duplicate declarations which is nice.



let x;
let x; // error!


Variables declared with let can also be used in closures which can be expected



let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms


What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:



// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }


Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?



I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.










share|improve this question
















I understand that let prevents duplicate declarations which is nice.



let x;
let x; // error!


Variables declared with let can also be used in closures which can be expected



let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms


What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:



// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }


Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?



I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.







javascript ecmascript-6






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jun 17 '15 at 19:36







Explosion Pills

















asked Jun 17 '15 at 18:32









Explosion PillsExplosion Pills

149k38225310




149k38225310








  • 2





    You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

    – loganfsmyth
    Jun 17 '15 at 18:40













  • possible duplicate of Javascript - "let" keyword vs "var" keyword

    – user663031
    Jun 17 '15 at 19:11











  • See also stackoverflow.com/questions/762011/….

    – user663031
    Jun 17 '15 at 19:11











  • Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

    – Anton Temchenko
    Jun 13 '16 at 17:49











  • Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

    – Marek
    Nov 19 '17 at 1:02
















  • 2





    You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

    – loganfsmyth
    Jun 17 '15 at 18:40













  • possible duplicate of Javascript - "let" keyword vs "var" keyword

    – user663031
    Jun 17 '15 at 19:11











  • See also stackoverflow.com/questions/762011/….

    – user663031
    Jun 17 '15 at 19:11











  • Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

    – Anton Temchenko
    Jun 13 '16 at 17:49











  • Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

    – Marek
    Nov 19 '17 at 1:02










2




2





You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

– loganfsmyth
Jun 17 '15 at 18:40







You've pretty much said it in your question, the let is essentially re-evaluated each time the loop iterates. I don't know that I'd call it syntactic sugar, it's just how loops are defined to work.

– loganfsmyth
Jun 17 '15 at 18:40















possible duplicate of Javascript - "let" keyword vs "var" keyword

– user663031
Jun 17 '15 at 19:11





possible duplicate of Javascript - "let" keyword vs "var" keyword

– user663031
Jun 17 '15 at 19:11













See also stackoverflow.com/questions/762011/….

– user663031
Jun 17 '15 at 19:11





See also stackoverflow.com/questions/762011/….

– user663031
Jun 17 '15 at 19:11













Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

– Anton Temchenko
Jun 13 '16 at 17:49





Read this 2 posts: LET in ECMAScript 6 - Block Scoped Variables , Constants in ECMAScript 6

– Anton Temchenko
Jun 13 '16 at 17:49













Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

– Marek
Nov 19 '17 at 1:02







Am I correct that in here: let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms we can also use var i = 100?

– Marek
Nov 19 '17 at 1:02














3 Answers
3






active

oldest

votes


















30















Is this just syntactic sugar for ES6?




No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
CreatePerIterationEnvironment.




How is this working?




If you use that let keyword in the for statement, it will check what names it does bind and then




  • create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)

  • copy the values from all variables with those names from one to the next environment


Your loop statement for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } desugars to a simple



// omitting braces when they don't introduce a block
var i;
i = 0;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;



while for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } does "desugar" to the much more complicated



// using braces to explicitly denote block scopes,
// using indentation for control flow
{ let i;
i = 0;
__status = {i};
}
{ let {i} = __status;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;






share|improve this answer


























  • This is probably more correct and certainly more concise than my answer.

    – ssube
    Jun 17 '15 at 19:16











  • @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

    – Kostas Dimakis
    Mar 1 '17 at 11:39











  • @KonstantinosDimakis which context do you mean?

    – Bergi
    Mar 1 '17 at 14:49











  • @Bergi the one with the let

    – Kostas Dimakis
    Mar 1 '17 at 14:54











  • It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

    – Bergi
    Mar 1 '17 at 15:04





















6














let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.



The MDN explanation supports this as well, stating that:




It works by binding zero or more variables in the lexical scope of a single block of code




suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.



In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.



Adapting your example to run in the browser:



// prints '10' 10 times
for (var i = 0; i < 10; i++) {
setTimeout(_ => console.log('var', i), 0);
}

// prints '0' through '9'
for (let i = 0; i < 10; i++) {
setTimeout(_ => console.log('let', i), 0);
}


certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:






for (var i = 0; i < 10; i++) {
setTimeout(function(_) {
return console.log(i);
}, 0);
}

var _loop = function(_i) {
setTimeout(function(_) {
return console.log(_i);
}, 0);
};

// prints '0' through '9'
for (var _i = 0; _i < 10; _i++) {
_loop(_i);
}





Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.






share|improve this answer


























  • @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

    – ssube
    Jun 17 '15 at 19:03






  • 1





    I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

    – Explosion Pills
    Jun 17 '15 at 19:06











  • @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

    – ssube
    Jun 17 '15 at 19:09



















5














I found this explanation from Exploring ES6 book the best:




var-declaring a variable in the head of a for loop creates a single
binding (storage space) for that variable:



const arr = ;
for (var i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [3,3,3]


Every i in the bodies of the three arrow functions refers to the same
binding, which is why they all return the same value.



If you let-declare a variable, a new binding is created for each loop
iteration:



const arr = ;
for (let i=0; i < 3; i++) {
arr.push(() => i);
}

arr.map(x => x()); // [0,1,2]


This time, each i refers to the binding of one specific iteration and
preserves the value that was current at that time. Therefore, each
arrow function returns a different value.







share|improve this answer

























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30899612%2fexplanation-of-let-and-block-scoping-with-for-loops%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    3 Answers
    3






    active

    oldest

    votes








    3 Answers
    3






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    30















    Is this just syntactic sugar for ES6?




    No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
    CreatePerIterationEnvironment.




    How is this working?




    If you use that let keyword in the for statement, it will check what names it does bind and then




    • create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)

    • copy the values from all variables with those names from one to the next environment


    Your loop statement for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } desugars to a simple



    // omitting braces when they don't introduce a block
    var i;
    i = 0;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;



    while for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } does "desugar" to the much more complicated



    // using braces to explicitly denote block scopes,
    // using indentation for control flow
    { let i;
    i = 0;
    __status = {i};
    }
    { let {i} = __status;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;






    share|improve this answer


























    • This is probably more correct and certainly more concise than my answer.

      – ssube
      Jun 17 '15 at 19:16











    • @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

      – Kostas Dimakis
      Mar 1 '17 at 11:39











    • @KonstantinosDimakis which context do you mean?

      – Bergi
      Mar 1 '17 at 14:49











    • @Bergi the one with the let

      – Kostas Dimakis
      Mar 1 '17 at 14:54











    • It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

      – Bergi
      Mar 1 '17 at 15:04


















    30















    Is this just syntactic sugar for ES6?




    No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
    CreatePerIterationEnvironment.




    How is this working?




    If you use that let keyword in the for statement, it will check what names it does bind and then




    • create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)

    • copy the values from all variables with those names from one to the next environment


    Your loop statement for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } desugars to a simple



    // omitting braces when they don't introduce a block
    var i;
    i = 0;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;



    while for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } does "desugar" to the much more complicated



    // using braces to explicitly denote block scopes,
    // using indentation for control flow
    { let i;
    i = 0;
    __status = {i};
    }
    { let {i} = __status;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;






    share|improve this answer


























    • This is probably more correct and certainly more concise than my answer.

      – ssube
      Jun 17 '15 at 19:16











    • @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

      – Kostas Dimakis
      Mar 1 '17 at 11:39











    • @KonstantinosDimakis which context do you mean?

      – Bergi
      Mar 1 '17 at 14:49











    • @Bergi the one with the let

      – Kostas Dimakis
      Mar 1 '17 at 14:54











    • It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

      – Bergi
      Mar 1 '17 at 15:04
















    30












    30








    30








    Is this just syntactic sugar for ES6?




    No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
    CreatePerIterationEnvironment.




    How is this working?




    If you use that let keyword in the for statement, it will check what names it does bind and then




    • create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)

    • copy the values from all variables with those names from one to the next environment


    Your loop statement for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } desugars to a simple



    // omitting braces when they don't introduce a block
    var i;
    i = 0;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;



    while for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } does "desugar" to the much more complicated



    // using braces to explicitly denote block scopes,
    // using indentation for control flow
    { let i;
    i = 0;
    __status = {i};
    }
    { let {i} = __status;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;






    share|improve this answer
















    Is this just syntactic sugar for ES6?




    No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
    CreatePerIterationEnvironment.




    How is this working?




    If you use that let keyword in the for statement, it will check what names it does bind and then




    • create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)

    • copy the values from all variables with those names from one to the next environment


    Your loop statement for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } desugars to a simple



    // omitting braces when they don't introduce a block
    var i;
    i = 0;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    i++;



    while for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } does "desugar" to the much more complicated



    // using braces to explicitly denote block scopes,
    // using indentation for control flow
    { let i;
    i = 0;
    __status = {i};
    }
    { let {i} = __status;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;
    if (i < 10)
    process.nextTick(_ => console.log(i))
    __status = {i};
    } { let {i} = __status;
    i++;







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Jul 20 '17 at 9:27

























    answered Jun 17 '15 at 19:07









    BergiBergi

    366k58546872




    366k58546872













    • This is probably more correct and certainly more concise than my answer.

      – ssube
      Jun 17 '15 at 19:16











    • @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

      – Kostas Dimakis
      Mar 1 '17 at 11:39











    • @KonstantinosDimakis which context do you mean?

      – Bergi
      Mar 1 '17 at 14:49











    • @Bergi the one with the let

      – Kostas Dimakis
      Mar 1 '17 at 14:54











    • It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

      – Bergi
      Mar 1 '17 at 15:04





















    • This is probably more correct and certainly more concise than my answer.

      – ssube
      Jun 17 '15 at 19:16











    • @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

      – Kostas Dimakis
      Mar 1 '17 at 11:39











    • @KonstantinosDimakis which context do you mean?

      – Bergi
      Mar 1 '17 at 14:49











    • @Bergi the one with the let

      – Kostas Dimakis
      Mar 1 '17 at 14:54











    • It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

      – Bergi
      Mar 1 '17 at 15:04



















    This is probably more correct and certainly more concise than my answer.

    – ssube
    Jun 17 '15 at 19:16





    This is probably more correct and certainly more concise than my answer.

    – ssube
    Jun 17 '15 at 19:16













    @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

    – Kostas Dimakis
    Mar 1 '17 at 11:39





    @Bergi loved your answer, but I still, I cannot really visualize how the execution context would look like. If you had the time could you please edit your answer to show how this would look like?

    – Kostas Dimakis
    Mar 1 '17 at 11:39













    @KonstantinosDimakis which context do you mean?

    – Bergi
    Mar 1 '17 at 14:49





    @KonstantinosDimakis which context do you mean?

    – Bergi
    Mar 1 '17 at 14:49













    @Bergi the one with the let

    – Kostas Dimakis
    Mar 1 '17 at 14:54





    @Bergi the one with the let

    – Kostas Dimakis
    Mar 1 '17 at 14:54













    It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

    – Bergi
    Mar 1 '17 at 15:04







    It's just a scope that contains one variable i. It is referenced by the closure, and it references the scope in which the loop was contained in as its outer link.

    – Bergi
    Mar 1 '17 at 15:04















    6














    let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.



    The MDN explanation supports this as well, stating that:




    It works by binding zero or more variables in the lexical scope of a single block of code




    suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.



    In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.



    Adapting your example to run in the browser:



    // prints '10' 10 times
    for (var i = 0; i < 10; i++) {
    setTimeout(_ => console.log('var', i), 0);
    }

    // prints '0' through '9'
    for (let i = 0; i < 10; i++) {
    setTimeout(_ => console.log('let', i), 0);
    }


    certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:






    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }





    Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.






    share|improve this answer


























    • @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

      – ssube
      Jun 17 '15 at 19:03






    • 1





      I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

      – Explosion Pills
      Jun 17 '15 at 19:06











    • @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

      – ssube
      Jun 17 '15 at 19:09
















    6














    let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.



    The MDN explanation supports this as well, stating that:




    It works by binding zero or more variables in the lexical scope of a single block of code




    suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.



    In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.



    Adapting your example to run in the browser:



    // prints '10' 10 times
    for (var i = 0; i < 10; i++) {
    setTimeout(_ => console.log('var', i), 0);
    }

    // prints '0' through '9'
    for (let i = 0; i < 10; i++) {
    setTimeout(_ => console.log('let', i), 0);
    }


    certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:






    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }





    Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.






    share|improve this answer


























    • @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

      – ssube
      Jun 17 '15 at 19:03






    • 1





      I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

      – Explosion Pills
      Jun 17 '15 at 19:06











    • @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

      – ssube
      Jun 17 '15 at 19:09














    6












    6








    6







    let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.



    The MDN explanation supports this as well, stating that:




    It works by binding zero or more variables in the lexical scope of a single block of code




    suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.



    In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.



    Adapting your example to run in the browser:



    // prints '10' 10 times
    for (var i = 0; i < 10; i++) {
    setTimeout(_ => console.log('var', i), 0);
    }

    // prints '0' through '9'
    for (let i = 0; i < 10; i++) {
    setTimeout(_ => console.log('let', i), 0);
    }


    certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:






    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }





    Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.






    share|improve this answer















    let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.



    The MDN explanation supports this as well, stating that:




    It works by binding zero or more variables in the lexical scope of a single block of code




    suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.



    In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.



    Adapting your example to run in the browser:



    // prints '10' 10 times
    for (var i = 0; i < 10; i++) {
    setTimeout(_ => console.log('var', i), 0);
    }

    // prints '0' through '9'
    for (let i = 0; i < 10; i++) {
    setTimeout(_ => console.log('let', i), 0);
    }


    certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:






    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }





    Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.






    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }





    for (var i = 0; i < 10; i++) {
    setTimeout(function(_) {
    return console.log(i);
    }, 0);
    }

    var _loop = function(_i) {
    setTimeout(function(_) {
    return console.log(_i);
    }, 0);
    };

    // prints '0' through '9'
    for (var _i = 0; _i < 10; _i++) {
    _loop(_i);
    }






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Jun 17 '15 at 19:06

























    answered Jun 17 '15 at 18:48









    ssubessube

    30.4k575116




    30.4k575116













    • @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

      – ssube
      Jun 17 '15 at 19:03






    • 1





      I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

      – Explosion Pills
      Jun 17 '15 at 19:06











    • @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

      – ssube
      Jun 17 '15 at 19:09



















    • @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

      – ssube
      Jun 17 '15 at 19:03






    • 1





      I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

      – Explosion Pills
      Jun 17 '15 at 19:06











    • @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

      – ssube
      Jun 17 '15 at 19:09

















    @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

    – ssube
    Jun 17 '15 at 19:03





    @TinyGiant Most browsers can't run the first snippet, as they tend not to support let and arrow functions.

    – ssube
    Jun 17 '15 at 19:03




    1




    1





    I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

    – Explosion Pills
    Jun 17 '15 at 19:06





    I had to read the spec several times -- I still don't think I fully understand but looking at Babel's output helps. Essentially using let does create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarations let i = 0, var j = 1 which would be incompatible anyway.

    – Explosion Pills
    Jun 17 '15 at 19:06













    @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

    – ssube
    Jun 17 '15 at 19:09





    @ExplosionPills The spec around scopes(/environments/bindings) tends to get a little obtuse. I still have a hard time understanding it, but it seems like the effective result is that each iteration has its own binding. Babel/ES6 doesn't let you mix let a = 1, const b = 2 either: you just can't mix different declaration types in a statement.

    – ssube
    Jun 17 '15 at 19:09











    5














    I found this explanation from Exploring ES6 book the best:




    var-declaring a variable in the head of a for loop creates a single
    binding (storage space) for that variable:



    const arr = ;
    for (var i=0; i < 3; i++) {
    arr.push(() => i);
    }
    arr.map(x => x()); // [3,3,3]


    Every i in the bodies of the three arrow functions refers to the same
    binding, which is why they all return the same value.



    If you let-declare a variable, a new binding is created for each loop
    iteration:



    const arr = ;
    for (let i=0; i < 3; i++) {
    arr.push(() => i);
    }

    arr.map(x => x()); // [0,1,2]


    This time, each i refers to the binding of one specific iteration and
    preserves the value that was current at that time. Therefore, each
    arrow function returns a different value.







    share|improve this answer






























      5














      I found this explanation from Exploring ES6 book the best:




      var-declaring a variable in the head of a for loop creates a single
      binding (storage space) for that variable:



      const arr = ;
      for (var i=0; i < 3; i++) {
      arr.push(() => i);
      }
      arr.map(x => x()); // [3,3,3]


      Every i in the bodies of the three arrow functions refers to the same
      binding, which is why they all return the same value.



      If you let-declare a variable, a new binding is created for each loop
      iteration:



      const arr = ;
      for (let i=0; i < 3; i++) {
      arr.push(() => i);
      }

      arr.map(x => x()); // [0,1,2]


      This time, each i refers to the binding of one specific iteration and
      preserves the value that was current at that time. Therefore, each
      arrow function returns a different value.







      share|improve this answer




























        5












        5








        5







        I found this explanation from Exploring ES6 book the best:




        var-declaring a variable in the head of a for loop creates a single
        binding (storage space) for that variable:



        const arr = ;
        for (var i=0; i < 3; i++) {
        arr.push(() => i);
        }
        arr.map(x => x()); // [3,3,3]


        Every i in the bodies of the three arrow functions refers to the same
        binding, which is why they all return the same value.



        If you let-declare a variable, a new binding is created for each loop
        iteration:



        const arr = ;
        for (let i=0; i < 3; i++) {
        arr.push(() => i);
        }

        arr.map(x => x()); // [0,1,2]


        This time, each i refers to the binding of one specific iteration and
        preserves the value that was current at that time. Therefore, each
        arrow function returns a different value.







        share|improve this answer















        I found this explanation from Exploring ES6 book the best:




        var-declaring a variable in the head of a for loop creates a single
        binding (storage space) for that variable:



        const arr = ;
        for (var i=0; i < 3; i++) {
        arr.push(() => i);
        }
        arr.map(x => x()); // [3,3,3]


        Every i in the bodies of the three arrow functions refers to the same
        binding, which is why they all return the same value.



        If you let-declare a variable, a new binding is created for each loop
        iteration:



        const arr = ;
        for (let i=0; i < 3; i++) {
        arr.push(() => i);
        }

        arr.map(x => x()); // [0,1,2]


        This time, each i refers to the binding of one specific iteration and
        preserves the value that was current at that time. Therefore, each
        arrow function returns a different value.








        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 22 '17 at 7:34









        Carles Alcolea

        3,08141336




        3,08141336










        answered Mar 15 '17 at 14:47









        swapnil_mishraswapnil_mishra

        30646




        30646






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30899612%2fexplanation-of-let-and-block-scoping-with-for-loops%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Lallio

            Unable to find Lightning Node

            Futebolista