Concurrency model and event loop
JavaScript has a concurrency model based on event loops, which are responsible for executing code, collecting and processing events, and executing subtasks in queues. This model is quite different from models found in other languages, such as C and Java.
The stack
The function call forms a stack of several frames.
function foo(b) { let a = 10; return a + b + 11; } function bar(x) { let y = 3; return foo(x * y); } console.log(bar(7)); / / return 42Copy the code
When bar is called, the first frame is created and pushed, containing the parameters and local variables of bar. When bar calls foo, a second frame is created and pushed on top of the first frame, which contains Foo’s arguments and local variables. When foo completes and returns, the second frame is popped off the stack (the remaining bar call frame). When the bar also completes and returns, the first frame is ejected and the stack is cleared.
The heap
Objects are allocated in a heap, a computer term used to denote a large (usually unstructured) area of memory.
The queue
Task A queue exists only for asynchronous tasks
All tasks can be divided into two types:
- If the synchronous task is queued on the main thread, the latter task can be executed only after the former task is completed
- Asynchronous tasks do not enter the main thread, and the main thread automatically reads the first part of the task queue, and the callback function associated with the task enters the main thread
Execution order: macro task first, then micro task
After the current macro task is executed, the system checks the microtask queue first. If there is a task, the task is executed first. Otherwise, the next macro task is executed
- Executed first
Synchronization task
, is also the Event Queue for macro tasks => micro tasks => Event Queue for macro tasks
A JavaScript runtime contains a message queue of messages to be processed. Each message is associated with a callback function that processes the message.
At some point during the event loop, the runtime processes messages in the queue starting with the first message to be queued. The processed message is removed from the queue and the function associated with it is called as an input parameter. As mentioned earlier, calling a function always creates a new stack frame for it.
Function processing continues until the stack is empty again; The event loop will then process the next message (if any) in the queue.
Event loop
Event Loop: The main thread reads the Event from the “task queue”
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Copy the code
queue.waitForMessage()
Waits synchronously for messages to arrive (if there are no messages currently waiting to be processed).
Execution to completion
After each message is fully executed, other messages are executed. This provides some excellent features for program analysis, including: when a function executes, it is not preempted, and only after it has finished does any other code run to modify the data that the function operates on. This is different from C, where, for example, if a function runs in a thread, it can be terminated anywhere and then run other code in another thread.
One drawback of this model is that when a message takes too long to complete, the Web application can’t handle interactions with the user, such as clicking or scrolling. To alleviate this problem, browsers typically pop up a “this script is running too long” dialog box. It is a good practice to shorten the single message processing time and, where possible, crop a message into multiple messages.
Add a message
In the browser, every time an event occurs and an event listener is bound to the event, a message is added to the message queue. If there is no event listener, the event will be lost. So when an element with a click event handler is clicked, it produces a similar message as any other event.
The setTimeout function takes two arguments: the message to be queued and an optional time value (default: 0). This value represents the minimum delay for the message to actually be enqueued. If there are no other messages in the queue and the stack is empty, the message will be processed immediately after this delay has passed. However, if there are other messages, the setTimeout message must wait for the other messages to finish processing. So the second parameter only represents the minimum delay time, not the exact wait time.
The following example demonstrates this concept (setTimeout does not execute directly after the timer expires) :
const s = new Date().getSeconds(); SetTimeout (function() {// output "2", Console. log("Ran after "+ (new Date().getseconds () -s) +" seconds"); }, 500); while(true) { if(new Date().getSeconds() - s >= 2) { console.log("Good, looped for 2 seconds"); break; }}Copy the code
Zero delay
Zero latency does not mean that the callback will execute immediately. Calling setTimeout with 0 as the second argument does not mean calling the callback immediately after 0 milliseconds.
The wait time depends on the number of messages waiting to be processed in the queue. In the following example, “This is a message” will be printed to the console before the callback gets processing, because the delay parameter is the minimum wait time required by the runtime to process the request, but is not guaranteed to be the exact wait time.
Basically, setTimeout needs to wait until all messages in the current queue have been processed before executing, even if the time specified by the second parameter has elapsed.
(function() {console.log(' This is the start '); SetTimeout (function cb() {console.log(' This is the message from the first callback '); }); Console. log(' This is a message '); SetTimeout (function cb1() {console.log(' This is the message from the second callback '); }, 0); Console. log(' This is the end '); }) (); // "This is the beginning" // "this is a message" // "this is the end" // "This is a message from the first callback" // "This is a message from the second callback"Copy the code
Multiple runtimes communicate with each other
A Web worker or a cross-domain iframe has its own stack, heap, and message queue. Two different runtimes can communicate only through the postMessage method. If another runtime listens for message events, this method adds messages to that runtime.
Never block
One very interesting feature of JavaScript’s event loop model that differs from many other languages is that it never blocks. Processing I/O is typically performed through events and callbacks, so while an application is waiting for an IndexedDB query to return or an XHR request to return, it can still handle other things, such as user input.
There are some exceptions for historical reasons, such as Alert or synchronous XHR, but they should be avoided as much as possible. Note that exceptions to exceptions do exist (but usually because of implementation errors rather than other reasons)