《面试系列》之什么是event loop

event loop指的是一种运行机制,规定了JS引擎是如何运行代码的。结合我为数不多的面试经历来看,基本算是必中的考点。如果要问到event loop,大致有如下流程(下面算是个人的经验总结,难免有遗漏之处,还请指正😊,顺序可能会有所打乱):

题目流程

为什么会有Event loop?(Why)

由于JS是单线程执行的,所以任务(代码)的执行需要进行排队。任务分为两种:同步和异步,针对异步任务,首先会被推到任务队列中,然后根据event loop机制进行执行。

event loop

对于此需要了解到两个很重要的知识点:宏任务微任务。 简单的理解为两个任务队列。

宏任务

当执行以下方法函数,将把其callback推到宏任务队列中

  1. setTimeoutsetIntervalsetImmediate
  2. I/O操作UI渲染script脚本执行
  3. MessageChannel(Vue的nexttick有使用)

微任务

同理,不过是推到微任务队列

  1. Promise
  2. MutationObserver
  3. process.nextTick (Node)

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

setTimeout(function callback1() {
console.log('a');
}, 0);

const p = new Promise(function callback2(resolve) {
console.log('b');
resolve();
});

p.then(function callback3() {
console.log('c');
});

console.log('d');

首先这块代码可以理解为一个script脚本,所以代码执行其实是从一个宏任务开始。
👇
当我执行setTimeout代码,这个时候会通过线程在0ms(不过浏览器最小会计算成4ms)后把callback1推到宏任务队列中。
👇
这个时候继续执行,实例Promise对象是同步的,所以这里会立即执行callback2,输出’b’,然后修改该Promise对象的状态为resolved
👇
因为当前对象p是resolved状态所以会执行对应的then方法,且将callback3推到微任务队列中。
👇
代码继续执行,输出’d’,主线程代码执行完毕。与微任务进行通信,并取出整队队列,依次执行。
👇
因为微任务队列只有callback3,执行输出’c’。这个时候一次eventloop循环结束,开始下一个循环。
👇
取出宏任务队列第一个任务,也就是上面推进的callback1,执行输出’a’.

所以最终输出’b’ -> ‘d’ -> ‘c’ -> ‘a’.不知你答对了吗?

Vue的nextTick机制

在一开始学习vue的时候,经常会遇到修改数据后,获取的DOM并不是最新的,然后查看资料说需要通过异步来处理,这里其实就是运用了event loop的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<p>{{count}}</p>
</template>

<script>
export default {
data() {
return {
count: 0,
}
},
mounted() {
this.count ++;
this.count ++;
// console.log(this.$el.textContent === ?) 这里我们发现输出为0,也就是初始值
}
}
</script>

简单的解释,执行mounted方法时,当第一次this.count ++时,首先会对count数据进行自增且Vue会将count对应Watcher的update方法推到微任务中;第二次this.count ++时,根据count对应Watcher对象的id来判断是否已经处理而选择跳过,只对count进行自增。然后在执行微任务中的update方法时对对应的模板进行更新操作。

由于微任务队列的执行顺序,所以我们只需引入一个微任务,该任务即可获取最新数据的DOM。或者通过下一次event loop循环的宏任务也可。


看过掘金小册《前端性能优化原理与实践》中有张介绍event loop的图。

循环过程

关于执行完微任务后再执行渲染操作,这个让我费解了许久,因为根据我们上面的解释,也就是在update方法后面的微任务即可获取到最新数据。

后面,解释是该渲染操作是把DOM进行paint,之前的update方法已经对DOM进行了修改处理。两者不是同一个概念。

推荐文章