Explanation of `let` and block scoping with for loops
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
add a comment |
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
2
You've pretty much said it in your question, theletis 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
add a comment |
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
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
javascript ecmascript-6
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, theletis 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
add a comment |
2
You've pretty much said it in your question, theletis 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
add a comment |
3 Answers
3
active
oldest
votes
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++;
…
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 thelet
– Kostas Dimakis
Mar 1 '17 at 14:54
It's just a scope that contains one variablei. 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
|
show 3 more comments
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.
@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 usingletdoes create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarationslet i = 0, var j = 1which 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 mixlet a = 1, const b = 2either: you just can't mix different declaration types in a statement.
– ssube
Jun 17 '15 at 19:09
add a comment |
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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++;
…
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 thelet
– Kostas Dimakis
Mar 1 '17 at 14:54
It's just a scope that contains one variablei. 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
|
show 3 more comments
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++;
…
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 thelet
– Kostas Dimakis
Mar 1 '17 at 14:54
It's just a scope that contains one variablei. 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
|
show 3 more comments
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++;
…
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++;
…
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 thelet
– Kostas Dimakis
Mar 1 '17 at 14:54
It's just a scope that contains one variablei. 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
|
show 3 more comments
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 thelet
– Kostas Dimakis
Mar 1 '17 at 14:54
It's just a scope that contains one variablei. 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
|
show 3 more comments
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.
@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 usingletdoes create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarationslet i = 0, var j = 1which 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 mixlet a = 1, const b = 2either: you just can't mix different declaration types in a statement.
– ssube
Jun 17 '15 at 19:09
add a comment |
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.
@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 usingletdoes create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarationslet i = 0, var j = 1which 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 mixlet a = 1, const b = 2either: you just can't mix different declaration types in a statement.
– ssube
Jun 17 '15 at 19:09
add a comment |
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.
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);
}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 usingletdoes create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarationslet i = 0, var j = 1which 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 mixlet a = 1, const b = 2either: you just can't mix different declaration types in a statement.
– ssube
Jun 17 '15 at 19:09
add a comment |
@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 usingletdoes create a new scope in each iteration. I suppose this is just built into the language. Babel also doesn't like mixing the declarationslet i = 0, var j = 1which 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 mixlet a = 1, const b = 2either: 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
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
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
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
2
You've pretty much said it in your question, the
letis 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