前言

面试中被问到事件循环,我以为我是懂的,结果真被问到的时候,发现一个结果:脑子以为懂了,手其实根本没懂,只是简单的知道同步任务,异步任务,宏任务,微任务,并没有深刻理解其执行原理,所以决定自己写一篇笔记,加深理解。

关于js的任务

  • js是按照语句顺序执行的

  • 同步任务:需要执行的任务在主线程上排队,一次执行

  • 异步任务:没有立马执行但是需要被执行的任务,放在任务队列里面

js的事件循环

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

  2. 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。

  3. js引擎存在monitoring process进程,会持续不断的检查主线程“执行栈”是否为空,一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

  4. 主线程不断重复上面的第三步,即为Event Loop(时间循环)。

如图所示:

异步任务

  • 宏任务:整体script代码 > setInterval/setTimeout

  • 微任务:process.nexttrick(nodejs的内容) > Promise

其执行流程为:

进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log(1);

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

new Promise(function (resolve) {
console.log(3);
resolve();
}).then(function() {
console.log(4);
}).then(function() {
console.log(5);
});

console.log(6);

下面是分析过程:

  1. console.log(1);:同步任务,推入主线程执行栈

  2. setTimeout:异步任务,属于宏任务,推入宏任务的Event Queue上

  3. new Promise:new函数为同步任务,推入主线程执行栈,then为异步任务,属于微任务,推入微任务的Event Queue上

  4. console.log(6);:同步任务,推入主线程执行栈

  5. 执行主线程执行栈:1 -> 3 -> 6

  6. 判断有没有微任务,执行微任务: 4 -> 5

  7. 执行下一个宏任务:2

练习

接下来找了一个复杂的例子,虽然复杂,但是只要懂了上面的js事件循环原理,也可以很轻易的推导出结果:

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
34
35
console.log(1);
setTimeout(function() {
console.log(2);
process.nextTick(function() {
console.log(3);
});
new Promise(function(resolve) {
console.log(4);
resolve();
}).then(function() {
console.log(5);
});
}, 1000);
process.nextTick(function() {
console.log(6);
});
new Promise(function(resolve) {
console.log(7);
resolve();
}).then(function() {
console.log(8);
});
setTimeout(function() {
console.log(9);
process.nextTick(function() {
console.log(10);
});
new Promise(function(resolve) {
console.log(11);
resolve();
}).then(function() {
console.log(12);
});
}, 0);
console.log(13);

这个结果是什么呢?试着分析一下(不写答案了,以备将来我忘记了,仍能当做练习题来看)