Home  >  Article  >  Web Front-end  >  Detailed explanation of JavaScript operating mechanism event loop (Event Loop)_javascript skills

Detailed explanation of JavaScript operating mechanism event loop (Event Loop)_javascript skills

WBOY
WBOYOriginal
2016-05-16 16:34:241297browse

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.

Copy code The code is as follows:

var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();

The req.send method in the above code is an Ajax operation to send data to the server. It is an asynchronous task, which means that the system will not read the "task queue" until all the codes of the current script are executed. Therefore, it is equivalent to the following writing.
Copy code The code is as follows:

var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};

In other words, it does not matter whether the part that specifies the callback function (onload and onerror) is before or after the send() method, because they are part of the execution stack, and the system will always execute them before reading the "task queue" ".

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.

Copy code The code is as follows:

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

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).

Copy code 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 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).

Copy code The code is as follows:

process.nextTick(function A() {
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, since the callback function specified by the process.nextTick method is always triggered at the end of the current "execution stack", not only function A is executed before the callback function timeout specified by setTimeout, but function B is also executed before timeout. This means that if there are multiple process.nextTick statements (regardless of whether they are nested), they will all be executed on the current "execution stack".

Now, look at setImmediate.

Copy code The code is as follows:

setImmediate(function A() {
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 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!

Copy code The code is as follows:

process.nextTick(function foo() {
process.nextTick(foo);
});

In fact, if you write recursive process.nextTick now, Node.js will throw a warning and ask you to change it to setImmediate.
In addition, since the callback function specified by process.nextTick is triggered in this "event loop", while the callback function specified by setImmediate is triggered in the next "event loop", it is obvious that the former always occurs earlier than the latter, and the execution efficiency is Also high (because there is no need to check the "task queue").
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn