Home > Article > Web Front-end > Detailed explanation of JavaScript operating mechanism event loop (Event Loop)_javascript skills
1. Why is JavaScript single-threaded?
A major feature of the JavaScript language is single-threading, which means that it can only do one thing at a time. So why can't JavaScript have multiple threads? This can improve efficiency.
JavaScript’s single thread is related to its purpose. As a browser scripting language, JavaScript's main purpose is to interact with users and manipulate the DOM. This determines that it can only be single-threaded, otherwise it will cause very complex synchronization problems. For example, suppose JavaScript has two threads at the same time. One thread adds content to a certain DOM node, and the other thread deletes the node. In this case, which thread should the browser use?
Therefore, in order to avoid complexity, JavaScript has been single-threaded since its birth. This has become a core feature of the language and will not change in the future.
In order to take advantage of the computing power of multi-core CPUs, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and must not operate the DOM. Therefore, this new standard does not change the single-threaded nature of JavaScript.
2. Task Queue
Single thread means that all tasks need to be queued, and the next task will not be executed until the previous task is completed. If the previous task takes a long time, the next task will have to wait.
If the queue is due to a large amount of calculation and the CPU is too busy, forget it, but many times the CPU is idle because the IO device (input and output device) is very slow (such as Ajax operations to read data from the network) , I have to wait for the results to come out before proceeding.
The designers of the JavaScript language realized that at this time, the CPU can completely ignore the IO device, suspend the waiting tasks, and run the later tasks first. Wait until the IO device returns the result, then go back and continue executing the suspended task.
As a result, JavaScript has two execution methods: one is that the CPU executes in sequence, the previous task ends, and then the next task is executed, which is called synchronous execution; the other is that the CPU skips tasks with long waiting times. , process the subsequent tasks first, which is called asynchronous execution. It is up to the programmer to choose which execution method to use.
Specifically, the operating mechanism of asynchronous execution is as follows. (The same goes for synchronous execution, since it can be considered an asynchronous execution without an asynchronous task.)
(1) All tasks are executed on the main thread, forming an execution context stack.
(2) In addition to the main thread, there is also a "task queue". The system puts the asynchronous tasks into the "task queue" and then continues to execute subsequent tasks.
(3) Once all tasks in the "execution stack" have been executed, the system will read the "task queue". If at this time, the asynchronous task has ended the waiting state, it will enter the execution stack from the "task queue" and resume execution.
(4) The main thread continues to repeat the third step above.
The picture below is a schematic diagram of the main thread and task queue.
As long as the main thread is empty, it will read the "task queue". This is the running mechanism of JavaScript. This process keeps repeating.
3. Events and callback functions
"Task queue" is essentially an event queue (can also be understood as a message queue). When the IO device completes a task, an event is added to the "task queue" to indicate that related asynchronous tasks can enter" Execution stack". The main thread reads the "task queue", which means reading the events in it.
Events in the "Task Queue", in addition to IO device events, also include some user-generated events (such as mouse clicks, page scrolling, etc.). As long as the callback function is specified, these events will enter the "task queue" when they occur, waiting for the main thread to read.
The so-called "callback function" is the code that will be hung up by the main thread. Asynchronous tasks must specify a callback function. When the asynchronous task returns from the "task queue" to the execution stack, the callback function will be executed.
"Task queue" is a first-in, first-out data structure. The events ranked first are returned to the main thread first. The reading process of the main thread is basically automatic. As soon as the execution stack is cleared, the first event on the "task queue" will automatically return to the main thread. However, due to the "timer" function mentioned later, the main thread needs to check the execution time, and certain events must return to the main thread at the specified time.
4. Event Loop
The main thread reads events from the "task queue". This process is cyclic, so the entire operating mechanism is also called Event Loop.
In order to better understand Event Loop, please look at the picture below (quoted from Philip Roberts' speech "Help, I'm stuck in an event-loop").
In the picture above, when the main thread is running, a heap and a stack are generated. The code in the stack calls various external APIs. They add various events (click, load, etc.) to the "task queue". done). As long as the code in the stack is executed, the main thread will read the "task queue" and execute the callback functions corresponding to those events in sequence.
The code in the execution stack is always executed before reading the "task queue". Take a look at the example below.
5. Timer
In addition to placing asynchronous tasks, "task queue" also has a function, that is, it can place timed events, that is, specify how much time certain code will be executed after. This is called the "timer" function, which is code that is executed regularly.
The timer function is mainly completed by the two functions setTimeout() and setInterval(). Their internal operating mechanisms are exactly the same. The difference is that the code specified by the former is executed once, while the latter is executed repeatedly. The following mainly discusses setTimeout().
setTimeout() accepts two parameters, the first is the callback function, and the second is the number of milliseconds to delay execution.
The execution results of the above code are 1, 3, 2 because setTimeout() delays the execution of the second line until 1000 milliseconds later.
If the second parameter of setTimeout() is set to 0, it means that after the current code is executed (the execution stack is cleared), the specified callback function will be executed immediately (0 millisecond interval).
The execution result of the above code is always 2, 1, because only after the second line is executed, the system will execute the callback function in the "task queue".
The HTML5 standard stipulates that the minimum value (shortest interval) of the second parameter of setTimeout() must not be less than 4 milliseconds. If it is lower than this value, it will automatically increase. Prior to this, older browsers set the minimum interval to 10 milliseconds.
In addition, those DOM changes (especially those involving page re-rendering) are usually not executed immediately, but every 16 milliseconds. At this time, the effect of using requestAnimationFrame() is better than setTimeout().
It should be noted that setTimeout() only inserts the event into the "task queue". The main thread must wait until the current code (execution stack) is finished executing before the main thread executes the callback function it specifies. If the current code takes a long time, it may take a long time, so there is no way to guarantee that the callback function will be executed at the time specified by setTimeout().
6. Event Loop of Node.js
Node.js is also a single-threaded Event Loop, but its operating mechanism is different from the browser environment.
Please see the diagram below (by @BusyRich).
According to the picture above, the operating mechanism of Node.js is as follows.
(1) V8 engine parses JavaScript scripts.
(2) The parsed code calls the Node API.
(3) The libuv library is responsible for the execution of Node API. It allocates different tasks to different threads to form an Event Loop (event loop), and returns the execution results of the tasks to the V8 engine in an asynchronous manner.
(4) The V8 engine returns the results to the user.
In addition to the two methods setTimeout and setInterval, Node.js also provides two other methods related to "task queue": process.nextTick and setImmediate. They can help us deepen our understanding of "task queue".
The process.nextTick method can trigger the callback function at the end of the current "execution stack" before the main thread reads the "task queue" next time. That is, the task it specifies always occurs before all asynchronous tasks. The setImmediate method triggers the callback function at the end of the current "task queue", that is to say, the task it specifies is always executed the next time the main thread reads the "task queue", which is very similar to setTimeout(fn, 0) . See the example below (via StackOverflow).
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
Now, look at setImmediate.
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// TIMEOUT FIRED
// 2
In the above code, there are two setImmediates. The first setImmediate specifies that the callback function A is triggered at the end of the current "task queue" (the next "event loop"); then, setTimeout also specifies that the callback function timeout is triggered at the end of the current "task queue", so in the output result, TIMEOUT FIRED ranks behind 1. As for 2 ranking behind TIMEOUT FIRED, it is because of another important feature of setImmediate: an "event loop" can only trigger one callback function specified by setImmediate.
We get an important difference from this: multiple process.nextTick statements are always executed at one time, while multiple setImmediate statements need to be executed multiple times. In fact, this is the reason why Node.js version 10.0 added the setImmediate method, otherwise the recursive call to process.nextTick like the following will be endless, and the main thread will not read the "event queue" at all!