This code logs 6
, 6 times:
(function timer() { for (var i=0; i<=5; i++) { setTimeout(function clog() {console.log(i)}, i*1000); } })();
But this code...
(function timer() { for (let i=0; i<=5; i++) { setTimeout(function clog() {console.log(i)}, i*1000); } })();
... logs the following result:
0 1 2 3 4 5
Why?
Is it because let
binds to the inner scope each item differently and var
keeps the latest value of i
?
var and let are both used for variable declaration in javascript but the difference between them is that var is function scoped and let is block scoped. Variable declared by let cannot be redeclared and must be declared before use whereas variables declared with var keyword are hoisted.
setTimeout allows us to run a function once after the interval of time. setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
setTimeout() The global setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires.
Technically it's how @rsp explains in his excellent answer. This is how I like to understand things work under the hood. For the first block of code using var
(function timer() { for (var i=0; i<=5; i++) { setTimeout(function clog() {console.log(i)}, i*1000); } })();
You can imagine the compiler goes like this inside the for loop
setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec
and so on
since i
is declared using var
, when clog
is called, the compiler finds the variable i
in the nearest function block which is timer
and since we have already reached the end of the for
loop, i
holds the value 6, and execute clog
. That explains 6 being logged six times.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With