JavaScript Event Loop Explained
JavaScript Execution Basics
One Thing at a Time
JavaScript is often described as a single-threaded language. This sounds technical, but the idea is simple: it can only do one thing at a time.
Imagine a chef in a kitchen who can only cook one dish from start to finish before moving on to the next. They can't start boiling water for pasta while also chopping vegetables for a salad. They must complete the chopping, then move on to the boiling. JavaScript's engine, the part of the browser that runs your code, works in a similar way. It executes one line of code, then the next, in a strict order.
This single-threaded nature means JavaScript code is synchronous. One task must finish before the next one can begin.
The Call Stack
So, if JavaScript can only do one thing at a time, how does it keep track of where it is in your code, especially when functions are calling other functions?
It uses something called a call stack. Think of it like a stack of plates. When you add a new plate, you put it on top. When you take a plate away, you always take it from the top. This is known as a "Last-In, First-Out" (LIFO) principle.
In JavaScript, every time a function is called, a new "plate" representing that function is added to the top of the stack. This "plate" is called a function execution context. When the function finishes and returns a value, its plate is removed from the top of the stack. The script then resumes whatever it was doing before that function was called.
function second() {
console.log('Hello from second!');
}
function first() {
console.log('Calling second...');
second();
console.log('...second has finished.');
}
// Start of the script
console.log('Calling first...');
first();
console.log('All done!');
Let's trace how the call stack manages this code.
- The script begins. The call stack is empty.
- The
first()function is called. Its execution context is pushed onto the stack. - Inside
first(), thesecond()function is called. Its execution context is pushed on top offirst()'s. second()runs itsconsole.logand finishes. Its context is popped off the stack.- Execution returns to where it left off in
first(). The nextconsole.logruns, and thenfirst()finishes. Its context is popped off the stack. - The script finishes, and the call stack is empty again.
Function Execution Context
We've mentioned the "function execution context" a few times. What is it, exactly? It’s the environment a function runs in. Every time a function is called, it gets its own execution context, which is a private little workspace for that specific call.
This context contains everything the function needs to do its job, including:
- Its local variables: Any variables declared inside the function only exist within this context.
- A reference to any outer variables it has access to.
- The value of
this.
When the function is done, its execution context is destroyed along with all its local variables. This is why a variable created inside one function isn't accessible from another, it's neatly contained within its own workspace.
What does it mean when we describe JavaScript as a "single-threaded" language?
The JavaScript call stack operates on a "Last-In, First-Out" (LIFO) principle. What does this mean?
Understanding this single-threaded model and the call stack is the first step. It provides the foundation for how JavaScript manages its workload, one task at a time.