Home >Web Front-end >JS Tutorial >Detailed explanation of JavaScript operating mechanism: Let's talk about Event Loop again
A year ago, I wrote an article "What is an Event Loop?" ", talked about my understanding of Event Loop.
Last month, I happened to see Philip Roberts’ speech "Help, I'm stuck in an event-loop". Only then did I realize with embarrassment that my understanding was wrong. I decided to rewrite this question to describe the inner workings of the JavaScript engine in detail, completely, and correctly. Below is my rewrite.
Before entering the text, insert a message. My new book "Introduction to ECMAScript 6" has been published (copyright page, inside page 1, inside page 2). It is printed in full color on coated paper and is very beautiful. It also comes with an index (of course the price is a little more expensive than similar books). To preview and purchase click here.
A major feature of the JavaScript language is that it is single-threaded, which means that it can only do one thing at a time. So why can't JavaScript have multiple threads? This can improve efficiency.
The single thread of JavaScript 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 the core feature of this 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 are not allowed to operate the DOM. Therefore, this new standard does not change the single-threaded nature of JavaScript.
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 (for example, Ajax operations read from the network (get data), you 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.
Therefore, JavaScript has two execution methods: one is that the CPU executes it 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 the waiting time For long tasks, the subsequent tasks are processed first. This 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 is true for synchronous execution, because it can be regarded as asynchronous execution without asynchronous tasks.)
(1) All tasks are executed on the main thread, forming an execution context stack (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 following figure 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.
"Task queue" is essentially a queue of events (can also be understood as a queue of messages). When the IO device completes a task, it is in the "task queue" Add an event to the "queue" to indicate that the related asynchronous task can enter the "execution stack". The main thread reads the "task queue", which means reading the events in it.
The 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" (callback) 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.
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 above figure, when the main thread is running, a heap and a stack are generated. The code in the stack calls various external APIs. They are in the "task" Add various events (click, load, done) to the queue". 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.
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 go to the server until all the codes of the current script are executed. Read "task queue". Therefore, it is equivalent to the following writing.
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 always Only after they are executed will the "task queue" be read.
In addition to placing asynchronous tasks, the "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.
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).
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() shall 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 requestAnimFrame() 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().
Detailed explanation of JavaScript operating mechanism: Lets talk about Event Loop again is also a single-threaded Event Loop, but its operating mechanism is different from the browser environment.
Please see the diagram below (author @BusyRich).
Based on the picture above, the operating mechanism of Detailed explanation of JavaScript operating mechanism: Lets talk about Event Loop again 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.
Detailed explanation of JavaScript operating mechanism: Lets talk about Event Loop again有一个process.nextTick()方法,可以将指定事件推迟到Event Loop的下一次执行,或者说放到"Detailed explanation of JavaScript operating mechanism: Lets talk about Event Loop again"的头部,也就是当前的执行栈清空之后立即执行。
function foo() { console.error(1); } process.nextTick(foo); console.log(2); // 2 // 1
process.nextTick(foo)的作用,与setTimeout(foo, 0)很相似,但是执行效率高得多。
以上就是JavaScript 运行机制详解:再谈Event Loop的内容,更多相关内容请关注PHP中文网(www.php.cn)!