Every time there’s a function call, the engine creates a “frame”. The frame contains all the internal variables and arguments for that function execution.
Now, what happens if there’s another function call inside? For example, if you call
exampleFunctionA(), but inside that function, you call
A new frame is created for that new function call, on top of the previous one. And if that function has yet another function inside? You guessed it, the engine creates a new frame. Each time a function execution finishes, the runtime engine deletes its frame and resumes the execution of the parent function.
A group of related frames are called “stacks”. If you call
exampleFunctionA(), which in turns calls
exampleFunctionB(), which itself runs
exampleFunctionC(), the frames for all three functions form a single stack.
When an event happens in the browser (i.e. mouse click, touchscreen tap, page load, drag and drop, etc.) and the event has an associated function, the engine creates a message (which contains the function call) and adds it to a queue, called “m.
The engine executes the function in the message, creating the stack and as many frames as needed. Once all function calls are finished, it moves on to the next message (or waits for new messages if there aren’t any yet).
setInterval functions also add messages to the queue. This means that those functions might have a delay if there are older messages in the queue. When an asynchronous task finishes, this also adds a new message to the queue.
If you have code that blocks the site, you could try to offload it to a web worker (as they have their own event loop, separate from the main page), or make use of asynchronous functions.