事件—事件循环机制-实例

在node服务器端运行以下代码会出现什么输出结果?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
setTimeout(function(){
console.log('setTimeout');
process.nextTick(function(){
console.log('nextTick1');
});
})

console.log('main1');

function say(){
console.log('hello! ');
process.nextTick(function(){
console.log('nextTick2');
})
}

new Promise(function(resolve){
process.nextTick(function(){
console.log('nextTick3');
})
console.log('promise 1');
resolve('promise then')
}).then(function(data){
console.log(data);
})

console.log('main2');

process.nextTick(function(){
console.log('nextTick4');
});

say();

下面就结合这个原理图,根据问题,来一步一步分析:

也可参考下图:

我们经常会听到引擎和runtime,它们的区别是什么呢?

  • 引擎:解释并编译代码,让它变成能交给机器运行的代码(runnable commands)。
  • runtime:就是运行环境,它提供一些对外接口供Js调用,以跟外界打交道。不同的runtime,会提供不同的接口,比如,在 Node.js 环境中,我们可以通过 require 来引入模块;而在浏览器中,我们有 window、 DOM。

Js引擎是单线程的,如上图中,它负责维护任务队列,并通过 Event Loop 的机制,按顺序把任务放入栈中执行。而图中的异步处理模块,就是 runtime 提供的,拥有和Js引擎互不干扰的线程。

任务队列

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。那么什么任务,会分到哪个队列呢?

  • 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • 微任务:process.nextTick, Promise, Object.observer, MutationObserver.

我们上面讲到,当stack空的时候,就会从任务队列中,取任务来执行。共分3步:

  1. 取一个宏任务来执行。执行完毕后,下一步。
  2. 取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。

从执行步骤来看,我们发现微任务,受到了特殊待遇!我们代码开始执行都是从script(全局任务)开始,所以,一旦我们的全局任务(属于宏任务)执行完,就马上执行完整个微任务队列。

我们代码的开始执行都是从script(全局任务)开始,这个全局任务属于宏任务;代码由上向下执行;

第1行,遇到了一个timer异步任务,属于宏任务,放入宏任务队列;

第8行,遇到log,内部没有其它函数,直接输出main1;

第10行,函数的定义,不执行;

第17行,遇到new Promise(),进入回调函数内部:
1) 遇到promise.nextTick,属于微任务,放入微任务队列nextTick3;
2) 遇到log,内部没有其它函数,直接输出promise 1;
3) 遇到resolve回调,属于微任务,放入微任务队列promise then;

第27行,遇到log,内部没有其它函数,直接输出main2;

第29行,遇到promise.nextTick,属于微任务,放入微任务队列nextTick4;

第33行,执行say():
1)遇到log,内部没有其它函数,直接输出hello;
2)遇到promise.nextTick,属于微任务,放入微任务队列nextTick2;
到此为止,我们已经做了如下事情:
1)宏任务队列中放入了一个timer函数;
2)输出了main1,promise 1,main2,hello1;
3)微任务队列中已经放入了promise then,nextTick3,nextTick4,nextTick2;

此时,我们的全局任务已执行完成了,就要马上执行完整个微任务队列。但是在微任务中,process.nextTick 是一个特殊的任务,它会被直接插入到微任务的队首(当然了,多个process.nextTick 之间也是先入先出的),优先级最高。所以,依次输出nextTick3,nextTick4,nextTick2,promise then

这时,执行栈为空了,可是别忘了,我们的宏任务队列还放者一个timer函数待执行,进入timer函数:
1)遇到log,内部没有其它函数,直接输出setTimeout;
2)遇到promise.nextTick,属于微任务,放入微任务队列nextTick1;

这个timer宏任务也执行完了,就马上执行完整个微任务队列,微任务队列目前只有一个任务,直接输出nextTick1;

这时,执行栈又为空了,还有其它任务吗? 没有了,大功告成;

以上的这种当函数执行栈为空,从任务队列中去一个任务来执行。再次为空,再取一个任务来执行,如此循环,这就是Event Loop,事件循环机制;

参考:

https://juejin.im/post/5a63470bf265da3e2c383068

https://segmentfault.com/a/1190000011198232