This article mainly introduces the EventLoop of the JavaScript running mechanism. This article explains EventLoop from multiple aspects. For more information, see
1. Why is JavaScript a single thread?
One of the major features of JavaScript is the single thread, which means that only one thing can be done at the same time. So why cannot JavaScript have multiple threads? This improves efficiency.
The single thread of JavaScript is related to its purpose. As a Browser Scripting Language, JavaScript is mainly used to interact with users and operate DOM. This determines that it can only be a single thread, otherwise it will bring a very complicated synchronization problem. For example, if JavaScript has two threads at the same time, one thread adds content to a DOM node, and the other thread deletes the node, which thread should the browser take as the standard?
Therefore, in order to avoid complexity, JavaScript is a single thread from its birth, which has become the core feature of the language and will not change in the future.
To utilize the computing capability of multi-core CPUs, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the sub-threads are completely controlled by the main thread and cannot operate the DOM. Therefore, this new standard does not change the nature of JavaScript single-thread.
Ii. task queue
A single thread means that all tasks need to be queued and the previous task ends before the latter task can be executed. If the previous task takes a long time, it will have to wait.
If the queue is too large for computing and the CPU is too busy, it may be okay, but the CPU is mostly idle because of the I/O device (input/output device) it is slow (for example, Ajax operations read data from the network) and has to wait for the results to come out before executing.
The designers of the JavaScript language realized that the CPU can suspend tasks in the waiting State regardless of the I/O device, and run the tasks listed below first. Wait until the IO Device returns the result and then go back and continue the pending task.
Therefore, JavaScript has two ways of execution: one is to execute the CPU in order, the previous task ends, and then execute the next task, which is called synchronous execution; the other is that the CPU skips a long wait time task and processes the subsequent task first, which is called asynchronous execution. The programmer chooses the execution method.
Specifically, the asynchronous execution mechanism is as follows. (Synchronous execution is also the case because it can be considered as an asynchronous execution without an asynchronous task .)
(1) All tasks are executed on the main thread to form an execution stack (execution context stack ).
(2) A "task queue" (task queue) exists in addition to the main thread ). The system puts asynchronous tasks in the "task queue" and continues to execute subsequent tasks.
(3) once all the tasks in the "execution stack" are completed, the system reads the "task queue ". If the waiting state of the asynchronous task is completed at this time, the task queue enters the execution stack and resumes execution.
(4) The main thread repeats step 3.
It is the main thread and task queue.
As long as the main thread is empty, it will read the "task queue", which is the JavaScript operating mechanism. This process will be repeated.
Iii. Events and callback Functions
"Task queue" is essentially an event queue (which can also be understood as a Message Queue). When an IO Device completes a task, it adds an event to "task queue, indicates that the related asynchronous task can enter the "execution stack. The main thread reads the "task queue", which is to read the events in it.
Events in the "task queue" include events generated by users (such as mouse clicks and Page scrolling) in addition to those of the I/O device ). As long as the callback function is specified, these events will enter the "task queue" and wait for the main thread to read.
The so-called "callback function" is the code that will be hung up by the main thread. The callback function must be specified for an asynchronous task. When an asynchronous task returns to the execution stack from the "task queue", the callback function is executed.
"Task queue" is a first-in-first-out data structure. The first event is returned to the main thread first. The reading process of the main thread is basically automatic. As long as the execution stack is empty, the first event in the "task queue" will be automatically returned to the main thread. However, because the "timer" function mentioned later exists, the main thread must check the execution time, and some events must be returned to the main thread at the specified time.
Iv. Event Loop
The main thread reads events from the "task queue". This process is cyclical, so the entire running mechanism is also called Event Loop ).
For a better understanding of Event Loop, see (translated from Philip Roberts's speech "Help, I'm stuck in an event-loop").
When the main thread is running, heap and stack are generated. The code in the stack calls various external APIs and they add various events (click, load, 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 the "task queue" is read. See the following example.
The Code is as follows:
Var req = new XMLHttpRequest ();
Req. open ('get', url );
Req. onload = function (){};
Req. onerror = function (){};
Req. send ();
Req in the above Code. the send method is used to send data to the server through Ajax operations. It is an asynchronous task, meaning that the system will read the "task queue" only after all the code of the current script is executed ". Therefore, it is equivalent to the following statement.
The Code is as follows:
Var req = new XMLHttpRequest ();
Req. open ('get', url );
Req. send ();
Req. onload = function (){};
Req. onerror = function (){};
That is to say, the onload and onerror parts of the specified callback function do not matter before or after the send () method, because they are part of the execution stack and the system always finishes executing them, to read the "task queue ".
5. Timer
In addition to placing asynchronous tasks, "task queue" can also be used to place scheduled events, that is, to specify how long some code will be executed. This is called the "timer" function, that is, the code for scheduled execution.
The timer function is mainly implemented by the setTimeout () and setInterval () functions. Their internal running mechanism is the same. The difference is that the Code specified by the former is one-time execution, the latter is executed repeatedly. The following describes setTimeout ().
SetTimeout () accepts two parameters. The first parameter is the callback function, and the second parameter is the number of milliseconds for deferred execution.
The Code is as follows:
Console. log (1 );
SetTimeout (function () {console. log (2) ;},1000 );
Console. log (3 );
The execution result of the above Code is 1, 3, 2, because setTimeout () delays the second line to 1000 milliseconds later.
If the second parameter of setTimeout () is set to 0, it indicates that the specified callback function is executed immediately (with an interval of 0 ms) after the current code is executed (the execution stack is cleared.
The Code is as follows:
SetTimeout (function () {console. log (1) ;}, 0 );
Console. log (2 );
The execution result of the above Code is always 2, 1, because the system will only execute the callback function in the "task queue" after the second line is executed.
The HTML5 Standard specifies the minimum value (minimum interval) of the second parameter of setTimeout (), which must be no less than 4 milliseconds. If it is lower than this value, it will automatically increase. Earlier versions of browsers set the shortest interval to 10 ms.
In addition, DOM changes (especially those involving page re-rendering) are usually not executed immediately, but every 16 milliseconds. In this case, the effect of using requestAnimationFrame () is better than that of setTimeout ().
Note that setTimeout () Only inserts the event into the "task queue" and must wait until the current Code (execution stack) is executed, the main thread will execute the callback function specified by it. If the current Code takes a long time, it may take a long time, so there is no way to ensure that the callback function will be executed at the time specified by setTimeout.
Vi. Node. js Event Loop
Node. js is also a single-threaded Event Loop, but its operating mechanism is different from that in the browser environment.
See the following (author @ BusyRich ).
The running mechanism of Node. js is as follows.
(1) parsing JavaScript scripts by the V8 engine.
(2) The parsed code calls the Node API.
(3) The libuv library is responsible for executing Node APIs. It assigns different tasks to different threads to form an Event Loop and asynchronously returns the execution results of the tasks to the V8 engine.
(4) the V8 engine returns the result to the user.
In addition to the setTimeout and setInterval methods, Node. js also provides two other methods related to "task queue": process. nextTick and setImmediate. They help us better understand "task queue.
The process. nextTick method can trigger the callback function at the end of the current "execution stack"-before the next time the main thread reads "task queue. That is to say, the specified task always occurs before all asynchronous tasks. The setImmediate method triggers a callback function at the end of the current "task queue". That is to say, the specified task is always executed when the main thread reads the "task queue" next time, this is similar to setTimeout (fn, 0. See the following example (via StackOverflow ).
The Code is as follows:
Process. nextTick (function (){
Console. log (1 );
Process. nextTick (function B () {console. log (2 );});
});
SetTimeout (function timeout (){
Console. log ('timeout fired ');
}, 0)
// 1
// 2
// TIMEOUT FIRED
In the above Code, because process. the callback function specified by the nextTick method is always triggered at the end of the current "execution stack". Therefore, not only does function A run before the callback function timeout specified by setTimeout, in addition, function B is executed first than timeout. This indicates that if there are multiple process. nextTick statements (no matter whether they are nested or not), they will all be executed in the current "execution stack.
Now, let's look at setImmediate.
The Code is as follows:
SetImmediate (function (){
Console. log (1 );
SetImmediate (function B () {console. log (2 );});
});
SetTimeout (function timeout (){
Console. log ('timeout fired ');
}, 0)
// 1
// TIMEOUT FIRED
// 2
In the above Code, there are two setImmediate. The first setImmediate specifies that callback function A is triggered at the end of the current "task queue" (next "event loop"). Then, setTimeout is also specified to trigger the callback function timeout at the end of the current "task queue". Therefore, in the output result, timeout fired is placed behind 1. 2 is behind timeout fired because setImmediate has another important feature: An "event loop" can only trigger one callback function specified by setImmediate.
An important difference is that multiple process. nextTick statements are always executed at a time, and multiple setImmediate statements must be executed multiple times. In fact, this is exactly the Node. the reason why the setImmediate method is added in js 10.0, otherwise the following recursive call process will be performed. nextTick will never end, and the main thread will not read the "event queue "!
The Code is as follows:
Process. nextTick (function foo (){
Process. nextTick (foo );
});
In fact, if you write recursive process. nextTick, Node. js will throw a warning asking you to change it to setImmediate.
In addition, because process. the callback function specified by nextTick is triggered in this "event loop", while setImmediate specifies to trigger in the next "event loop". Therefore, it is clear that the former always occurs earlier than the latter, the execution efficiency is also high (because you do not need to check "task queue ").