Why Let and Var Bindings Behave Differently Using Settimeout Function

Why let and var bindings behave differently using setTimeout function?

With var you have a function scope, and only one shared binding for all of your loop iterations - i.e. the i in every setTimeout callback means the same variable that finally is equal to 6 after the loop iteration ends.

With let you have a block scope and when used in the for loop you get a new binding for each iteration - i.e. the i in every setTimeout callback means a different variable, each of which has a different value: the first one is 0, the next one is 1 etc.

So this:

(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}
})();

is equivalent to this using only var:

(function timer() {
for (var j = 0; j <= 5; j++) {
(function () {
var i = j;
setTimeout(function clog() { console.log(i); }, i * 1000);
}());
}
})();

using immediately invoked function expression to use function scope in a similar way as the block scope works in the example with let.

It could be written shorter without using the j name, but perhaps it would not be as clear:

(function timer() {
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}(i));
}
})();

And even shorter with arrow functions:

(() => {
for (var i = 0; i <= 5; i++) {
(i => setTimeout(() => console.log(i), i * 1000))(i);
}
})();

(But if you can use arrow functions, there's no reason to use var.)

This is how Babel.js translates your example with let to run in environments where let is not available:

"use strict";

(function timer() {
var _loop = function (i) {
setTimeout(function clog() {
console.log(i);
}, i * 1000);
};

for (var i = 0; i <= 5; i++) {
_loop(i);
}
})();

Thanks to Michael Geary for posting the link to Babel.js in the comments. See the link in the comment for a live demo where you can change anything in the code and watch the translation taking place immediately. It's interesting to see how other ES6 features get translated as well.

let vs var in javascript

First of all, the output will be four times and not five times(as mentioned in your comment).
I pasted your code in Babel REPL and this is what I got,

"use strict";

var arr = [1, 2, 3, 4];

var _loop = function _loop(i) {
setTimeout(function () {
console.log(arr[i]);
}, 1000);
};

for (var i = 0; i < arr.length; i++) {
_loop(i);
}

Do you see how let works internally now? :-)

let' in for loop with setTimeout()

If you can't use let, you can always use an Immediately Invoked Function Expression:

let timer = () => {  for (var i = 0; i < 5; i++) {    (i => {      setTimeout(() => {        console.log(i)      }, i * 1000)    })(i)  }}timer();

What is the difference between let and var?

Scoping rules

The main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).

function run() {
var foo = "Foo";
let bar = "Bar";

console.log(foo, bar); // Foo Bar

{
var moo = "Mooo"
let baz = "Bazz";
console.log(moo, baz); // Mooo Bazz
}

console.log(moo); // Mooo
console.log(baz); // ReferenceError
}

run();

What is the difference between let and var?

Scoping rules

The main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).

function run() {
var foo = "Foo";
let bar = "Bar";

console.log(foo, bar); // Foo Bar

{
var moo = "Mooo"
let baz = "Bazz";
console.log(moo, baz); // Mooo Bazz
}

console.log(moo); // Mooo
console.log(baz); // ReferenceError
}

run();

Explanation of `let` and block scoping with for loops

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++;



Related Topics



Leave a reply



Submit