JavaScript operational mechanism and Event Loop (event loop)

A, JavaScript single threaded

As we all know is a single-threaded JavaScript language, that is to say, JS can only do one thing at a time. Why JavaScript can not have more than one thread of it? Would not it be able to improve efficiency?

JavaScript is single-threaded, and its related purposes. As a browser scripting language, JavaScript main purpose is to interact with the user, as well as operating DOM. This determines that it can only be a single-threaded, otherwise it will bring a very complex synchronization problems. For example, suppose JavaScript while there are two threads, one thread to add content on a DOM node, another thread to delete this node, then the browser which thread should prevail?

So, to avoid complexity, from the birth, JavaScript is single-threaded, which has become a central feature of this language, will not change.

To take advantage of the computing power of multi-core CPU, HTML5 Web Worker proposed standard that allows JavaScript scripts to create multiple threads, but the main thread child thread completely under control, and shall not operate DOM. So, the new standard does not change the nature of single-threaded JavaScript.

Second, the task queue

Single-threaded means, all tasks need to line up, before the end of a mission, will perform a post-task. If a task takes a long time ago, a task after it had been waiting for.

If the queue is because computationally intensive, CPU busy, they do well, but many times the CPU is idle, because the IO device (input and output devices) is very slow (such as Ajax operation reads data from the network), had waiting for the results come out, and then execute down.

Designers realized JavaScript language, then the main thread can regardless of IO devices, hangs in the waiting task, run the task at the back. IO device returns to wait until the results come back, the pending tasks to continue execution.

Thus, all tasks can be divided into two, one is a synchronization task (Synchronous), the other is asynchronous task (asynchronous). Synchronization task means that queued for execution on the main thread task, only the first task is finished to a task after execution; asynchronous tasks mean, do not enter the main thread, and enter the "Task Queue" (task queue) of tasks, only "task queue" notify the main thread, an asynchronous tasks can be carried out, the task will enter the main thread.

In particular, the operating mechanism of asynchronous execution is as follows. (Synchronous execution, too, because it can not be regarded as asynchronous execution asynchronous tasks.)

(1) All synchronization tasks are performed on the main thread to form a execution stack (execution context stack).

Addition (2) the main thread, there is a "Task Queue" (task queue). With asynchronous tasks run as long as a result, an event is placed in the "task queue" at home.

(3) Once the "execution stack" all synchronization task is finished, the system will read the "task queue" to see what events there. Those corresponding asynchronous tasks, thus ending the wait state, into the execution stack, started.

(4) the main thread is repeated the third step above.

The figure is a schematic view of the main thread and the task queue.

Task Queue

As long as the main thread empty, it will go read "Task Queue", which is the operational mechanism of JavaScript. This process will be repeated.

Third, events and callbacks

"Task Queue" is an event queue (queue can also be understood as a message), IO devices to complete a task, you add an event in "Task Queue" in it indicates that the associated asynchronous task may enter the "execution stack" the. The main thread reads "task queue", which is to read the events there.

"Task Queue" in the event, in addition to the events IO devices, also includes a number of user-generated events (such as mouse clicks, page scrolling, etc.). Just specify the callback function too, will enter the "Task Queue" When these events occur, wait for the main thread to read.

The so-called "callback" (callback), that is, those codes will be hung up in the main thread. Asynchronous tasks must specify a callback function, when the main thread begins execution asynchronous task is to perform the corresponding callback function.

"Task Queue" is a FIFO data structure, standing in the front of the event, the priority is read the main thread. The main thread of the reading process is essentially automatic, as long as the implementation of a stack of empty, "Task Queue" one of the first events will automatically enter the main thread. However, due to the existence of "Timer" function mentioned later, the main thread should first check the execution time, certain events only to specified time, to return to the main thread.

四、Event Loop

The main thread reads from "Task Queue" in the event, the process is constantly circulating, so the entire operation of this mechanism is also known as Event Loop (event loop).

To better understand the Event Loop, see below (quoted from Philip Roberts's speech "Help, the I'm Stuck in AN-Event Loop" ).

Event Loop

When the figure above, the main thread running, producing heap (heap) and stack (stack), the stack of code calls various external API, they are added to various events (click, load, done) in "Task Queue" in. As long as the stack of code is completed, go to the main thread will read "Task Queue", followed by the implementation of those events corresponding to the callback function.

Stack code (sync task), always read "Task Queue" (asynchronous task) performed before. Consider the following example.


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

req.send method above code is Ajax operation to send data to the server, it is an asynchronous task, all code means that only the current script is executed, the system will be to read the "task queue." Therefore, it is equivalent to the following wording.


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

That is, the portion (the onload and the onerror) specify a callback function, before or after the send () method does not matter, because they are part of the execution stack, they are always executed finished, only to read the "Task Queue . "

Fifth, the timer

除了放置异步任务的事件,"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。

定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。

setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。


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

上面代码的执行结果是1,3,2,因为setTimeout()将第二行推迟到1000毫秒之后执行。

如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。


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

上面代码的执行结果总是2,1,因为只有在执行完第二行以后,系统才会去执行"任务队列"中的回调函数。

总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

六、Node.js的Event Loop

Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。

请看下面的示意图(作者@BusyRich)。

Node.js

根据上图,Node.js的运行机制如下。

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8引擎再将结果返回给用户。

除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTicksetImmediate。它们可以帮助我们加深对"任务队列"的理解。

process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via StackOverflow)。


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 

上面代码中,由于process.nextTick方法指定的回调函数,总是在当前"执行栈"的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。

现在,再看setImmediate。


setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0); 

上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。

令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。


setImmediate(function (){ setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0); }); // 1 // TIMEOUT FIRED // 2 

上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1--TIMEOUT FIRED--2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。

我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取"事件队列"!


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

In addition, because process.nextTick specified callback function is in this "event loop" trigger, and setImmediate is specified at the next "event loop" trigger, so it is clear that the former always occurs earlier than the latter, and efficiency too high (because there is no check "task queue").

Guess you like

Origin www.cnblogs.com/songyao666/p/11442360.html