JavaScript Event Loop and Call Stack: Understanding Function Execution Order

JavaScript Event Loop and Call Stack: Understanding Function Execution Order

We want to check order running these functions and check javascript run time under the hood:

console.log('a');        //step1
(function() { console.log('b'); })();  //step2
setTimeout(() => { console.log('c'); });  //step3
setTimeout(() => { console.log('d'); }, 0);  //step4
Promise.resolve().then(() => { console.log('e'); });  //step5
console.log('f');        //step6        

We will break down the execution step-by-step and explain how JavaScript runs these functions in the background.

Understanding Key Concepts

Before jumping into the execution order, it’s important to understand a few concepts:

1.Hoisting: JavaScript performs hoisting, which is the behavior of moving variable and function declarations to the top of their containing scope during compile time. This can affect the order in which things appear to be declared or executed.

However, hoisting only works with function declarations and variable declarations (with var), not with function expressions or let/const variables. This means that variables declared with let or const, as well as function expressions (like the anonymous function in our example), do not get hoisted.


2.Call Stack: The call stack is a data structure used by JavaScript to keep track of function calls. When a function is called, it’s pushed onto the stack, and when it finishes, it is popped off. JavaScript runs synchronously and executes one function at a time.


3.Non-blocking: refers to the ability of certain operations (such as asynchronous tasks like I/O operations, network requests, or timers) to execute independently in the background, without blocking the main thread. JavaScript is single-threaded, meaning it runs one task at a time. However, asynchronous tasks are handled using event-driven mechanisms, allowing the main thread to continue executing while waiting for the asynchronous task to complete.


4.Event loop: The event loop allows JavaScript to handle asynchronous operations without blocking the main thread. When an asynchronous function like setTimeout or Promise is invoked, it is placed in a task queue or microtask queue and is processed when the call stack is empty.

Step-by-Step Breakdown of Execution

Now let’s go through the code to see what happens when it runs:

1. console.log('a')

This is a simple synchronous operation. It gets pushed onto the call stack and immediately logs 'a' to the console.

2. (function() { console.log('b'); })()

This is an immediately invoked function expression (IIFE). The anonymous function is executed immediately after it’s defined. It gets pushed onto the call stack, executes, and 'b' is logged to the console.

3. setTimeout(() => { console.log('c'); })

The setTimeout function is asynchronous and non-blocking. Even though it is set to execute after a specified delay, it doesn’t run immediately. If no delay is specified, the default delay is 0 milliseconds. However, this doesn’t mean the callback runs right away. Instead, the callback function is moved to the task queue (also known as the macro task queue). The event loop will process this task once the call stack is empty, so the callback will not be executed until all synchronous code has finished running. This means that the logging of 'c' will happen after the synchronous operations are completed.

4. setTimeout(() => { console.log('d'); }, 0)

Similar to the previous setTimeout, this function also goes to the task queue and waits to be executed after the call stack is cleared. 'd' will be logged after 'c'.

5. Promise.resolve().then(() => { console.log('e'); })

The Promise is a microtask, which means it has higher priority over tasks in the task queue. As soon as the synchronous code is finished (i.e., after the call stack is empty), the event loop processes the microtask queue. So, 'e' will be logged before the tasks from the setTimeout.

6. console.log('f')

This is another synchronous operation. It gets pushed onto the call stack, and 'f' is logged to the console.


After the synchronous operations have completed (steps 1, 2, and 6), the event loop moves to the microtask queue and executes the promise (step 5). Once the microtasks are finished, the event loop proceeds to the task queue, processing the setTimeout functions (steps 3 and 4).

Thus, the final order of execution is:

a
b
f
e
c
d        

? Synchronous Code: The synchronous operations (console.log('a'), (function() { console.log('b'); })(), and console.log('f')) are executed first and immediately. These run in the order they appear in the code.

? Microtasks: The Promise creates a microtask that has higher priority than regular tasks in the task queue. Therefore, 'e' gets logged after the synchronous code finishes but before the asynchronous setTimeout tasks.

? Tasks in the Task Queue: Both setTimeout functions are placed in the task queue and wait until the call stack is empty and the microtasks are processed. Since they are placed with 0 milliseconds delay, they are processed in the order they appear: 'c' first, then 'd'.

Conclusion


Understanding how JavaScript handles synchronous and asynchronous code is essential for optimizing performance and debugging tricky issues. The event loop, call stack, and task queues provide a non-blocking, efficient mechanism for executing code, allowing for complex tasks like handling user interactions and processing data in the background without freezing the UI. By mastering the concepts of hoisting, the call stack, and the event loop, developers can gain greater control over their JavaScript execution flow, improving both performance and reliability.


要查看或添加评论,请登录

Farshad Karimi的更多文章

  • How to get rid of memory leak In javascript

    How to get rid of memory leak In javascript

    In JavaScript, memory management is largely handled by the engine, but understanding how the Heap, Garbage Collection…

    2 条评论

社区洞察

其他会员也浏览了