Var and Let in for loop with setTimeout - Event loop and block scoping
Ahmed Hasan
Posted on December 20, 2022
Does everyone just tell you that the difference is that Let is block scoped and var is not?
Well, you are not alone - Me too😒.
I have had enough of these explanations. Let’s really understand it.
This for loop code block requires understanding of “Event loop” and “the differences between block and functional scoping”
I will try to break these concepts down for you first, so you can understand what is going on.
Let’s start with the Block scoping. — What is a block in JavaScript code?
A block statement is used to group zero or more statements. The block is delimited by a pair of braces (“curly brackets”)
➡️➡️ Code Time 🙋♂️🥳🎉🎆
var withVar = 1
let withLet = 1
if (true) {
var withVar = 2
let withLet = 2
}
console.log(withVar) //2
console.log(withLet) //1
Well, why is that? See⬇️⬇️
var withVar = 1
let withLet = 1
if (true) {
var withVar = 2
let withLet = 2
}
console.log(withVar) //2
console.log(withLet) //1
// This code ⬆️⬆️ is equivelat to this ⬇️⬇️
var withVar = 1
let withLet = 1
var withVar = 2 //⬅️⬅️
if (true) {
let withLet = 2
}
console.log(withVar) //2
console.log(withLet) //1
But still, why!
Well, because the difference between (var / let and const).
_Let _is block scoped and var is not. — *var is functionally or globally scoped
*
This means that in each block, this block creates it’s unique _let _variable.
but with var it declares it globally or functionally if we are inside of a scope of function. We will see that in coming code.
Let’s simplify scoping, local scope and global scope terms.
In the simplest way ever: We can dig out, but we can’t dig in :)
/**
🙋♂️🙋♂️ Don't be overwhelmed, See it line by line 👁️👁️
*/
{var withVar = 1}
{let withLet = 2}
console.log(withVar)
// console.log(withLet) // ❌ReferenceError: withLet is not defined - because it is block scoped
//-----------------------------------------------------------------------------------------------------------------------
let globallyDefinedVar = 'globallyDefinedVar data'
function dummyFunc(dummyFuncArg){
console.log(globallyDefinedVar) // digging out and reaching global scope
const scopedInDummyFunc = 'scopedInDummyFunc data'
function innerFunc(){
console.log(globallyDefinedVar) // digging out and reaching global scope
console.log(dummyFuncArg) // digging out and reaching dummyFunc scope (it's arguments)
console.log(scopedInDummyFunc) // digging out and reaching dummyFunc scope
let scopedInInnerFunc = 'scopedInInnerFunc data'
var scopedInInnerFuncWithVar = 'scopedInInnerFuncWithVar data'
}
innerFunc()
// console.log(scopedInInnerFunc) // ❌ReferenceError: scopedInInnerFunc is not defined
// console.log(scopedInInnerFuncWithVar) //❌ReferenceError: scopedInInnerFuncWithVar is not defined - because it is functionally scoped - i can't dig in
}
dummyFunc('dummyFuncArg data')
Let’s simplify event loop, microtask queue, macrotask queue, callback queue, single-threaded and browser Api terms
I well try to make it short and simple.
I will just focus on event loop with macro task and callback queues here.
if you want farther explanation on how and why event loop deals with asynchronous operations just Let🫡 me know.
➡️➡️Time for you to really focus with me :)
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
/**
* First
* Third
* Second
*/
Keep looking 👁️ at the gif ⬆️⬆️ while reading my explanation.
In perfect Utopian world where birds can sing, code is running line by line.
but sometimes we encounter an asynchronous operation.
well, JavaScript can not handle 2 operations at once.
there are many ways to make an operation wait until we handle the other one.
In our case ⬆️ setTimeout is not a built-in JavaScript method. it is provided by the browser.
Event loop keeps on executing Synchronous code until it encounters setTimeout (async), it makes it go to wait in (get handled by) the browser for (n) time.
I say enough talking, Let’s break it down.
JavaScript has it’s (main/single thread — call stack) where code runs in a sync way (line after line)
When foo gets executed, it gets added to the call stack, and because it does it’s work immediately by printing “First”, it gets popped out of the stack (in a FILO way).
—
When bar gets executed, it gets added to the call stack.
it has setTimeout method which gets added to the call stack.
“bar is not popped out because it didn’t finish it’s work yet”
setTimeout has a callback function which runs asynchronously.
the browser tells the JavaScript engine: “Hey, This is mine 😠🤬 — i well take care of it for (n) of time then give it back to you. just go help yourself 😠”
—
JavaScript is a good boy “Kinda”.
- setTimeout did it’s work, therefore it gets popped out of the stack.
- therefore, bar did it’s work, therefore it gets popped out of the stack. — baz gets executed, and gets added to the call stack, and because it does it’s work immediately by printing “Third”, it gets popped out of the stack.
Do you know what is going on in the background while we are chitchatting here?
I think you do :)
The browser is handling our callback.
after (n) time, it gives it back to us, but it is waiting in the (callback queue / macro tasks queue) until the callstack is empty.
Now, after the callstack is empty, the callback function gets added to the call stack (in a FIFO way), and because it does it’s work immediately by printing “Second”, it gets popped out of the stack.
I hope it is all clear now 🫡🫡
Let’s get back to our main code blocks.
for(var i = 0; i < 3; i++){
setTimeout(()=> console.log(i), 0)
}
//-----------OR-------
var i = 0
for(; i < 3; i++){
setTimeout(()=> console.log(i), 0)
}
//----------------------------------------------------------
for(let i = 0; i < 3; i++){
setTimeout(()=> console.log(i), 0)
}
//-----------OR-------
for(let i = 0; i < 3; i++){
i = i
setTimeout(() => console.log(i), 0)
}
1️⃣ With var
Var here is globally scoped right?
for loop gets executed and added to the call stack (it’s block will run 3 times)
— — 1st loop
var i = 0 //➡️ it will be attached (hoisted) in the global scope //📝 note that the initialization happens only in first loop, upcoming loops starts from the checking part
0 < 3 ? // true
Do something (our block)
i = i+1
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback (with the same reference to the globally defined i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
While the callback is being handled by the browser, it doesn’t have the value of (i).
It has it’s Reference in memory, and because of that when it comes back and get added to the call stack it will tell JS engine: “Give me value of (i), You son of Brendan Eich 🤬”
⬆️⬆️these 3 lines of explanations are the whole game changer for me.
If u r interested, I got them while i am in the bathroom🫡.
— — 2nd loop
1 < 3 ? // true
Do something (our block)
i = i+1
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback (with the same reference to the globally defined i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
— — 3rd loop
2 < 3 ? // true
Do something (our block)
i = i+1 — — → it will be 3
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback (with the same reference to the globally defined i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
— — 4th loop (Don’t get confused, there will be no 4th loop)
3 < 3 ? // false // ➡️➡️ Remember that last value of (i) is 3
for loop finished it’s work, it gets popped out of the stack.
JS Engine to the callback queue: “Hey you, I still have Synchronous work to do, I Will not take your callbacks. not yet”
2️⃣ With Let
Let is block scoped right?
Don’t worry, you will see what this actually means here :)
for loop gets executed and added to the call stack (it’s block will run 3 times)
The secret here is: with every iteration (every loop) we are creating a whole new (i), it is not the same i as before.
in second loop We can call it (Ï) or even call it (x).
in third loop We can call it (i with 3 dots 🫡) or even call it (whatever 🫡).
So, when setTimeout callback function takes the reference of (i).
each time it will be a different (i) in memory.
— — 1st loop
let i = 0 //➡️ it will be attached and hoisted in the block scope of for.
📝 note that the initialization happens only in first loop, upcoming loops starts from the checking part
0 < 3 ? // true
Do something (our block)
i = i+1
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback with (a whole new reference of i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
— — 2nd loop
1 < 3 ? // true
Do something (our block)
i = i+1
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback with (a whole new reference of i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
— — 3rd loop
2 < 3 ? // true
Do something (our block)
i = i+1
—
setTimeout gets added to the call stack, it does it’s work by sending it’s callback with (a whole new reference of i) to the browser to handle.
Then it gets popped out of the stack.
for loop didn’t finish it’s work yet.
— — 4th loop (Don’t get confused, there will be no 4th loop)
3 < 3 ? // false // ➡️➡️ last value of (i) is 3 but it won’t matter and we will see.
for loop finished it’s work, it gets popped out of the stack.
JS Engine to the callback queue: “Hey you, I finished my synchronous code and the call stack is empty, give me the 6 callbacks you have but in a (First In First Out) way, don’t cheat 😒”
All first 3 callbacks are coming searching for the reference (i).
Remember?
At this time the (i) the callback is looking for is referring to 3.
This will be done to all 3 callbacks.
As you may expected by now.
every callback of the remaining 3 has a reference to a whole different (i).
Each callback function will ask for the value of the reference it holds.
So we will have 0, 1, 2
We will not have 3 because there is no callback calling for a reference that holds a value of 3. ⬇️⬇️
Since the for loop finished before it can send more callbacks to the browser.
At the end.
All JavaScript concepts are connected.
It is a whole process.
For example, our code contains another concept which is Closure.
but i didn’t want to give you headache 😒.
Posted on December 20, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.