事件循环

前言

首先,得明确一点,JavaScript 是一门单线程语言。也就是说,JavaScript 代码是同步执行的,所有的代码都是由上而下执行的:

1
2
3
4
5
var name = 'nick'
console.log(name)

var age = 18
console.log(age)

但是如果代码量过大,有些代码的逻辑又复杂,一直不能运行完毕,后面的代码就无法运行,这就会造成一些堵塞的问题。就好比你去公交车站排队,等的人多了,你就必须等前面的人一个一个进入车里面,你才能再进去。

同步任务和异步任务

JavaScript 中,整体代码的运行我们可以称之为同步任务,也就是一行一行的执行;而另一些代码,可以不必等待前面的代码执行完毕之后再去执行,我们称之为异步任务。比方说,我们打开一个网页,页面骨架元素的加载就等同于同步任务,而网页上图片这种耗时的加载就等同于异步任务。

这里借用社区的一张图片:

解释一下图片上的内容:

  • 当一段代码开始运行时,会检查里面的同步任务及异步任务;同步任务进入主线程,异步任务进入Event Table
  • Event Table会注册异步任务回调函数,然后放进Event Queue
  • 当主线程中的任务全部执行完毕后,回去Event Queue中读取对应的异步任务,放进主线程中执行。
  • 不断重复上面的步骤。

上述内容就是我们常说的事件循环(Event Loop)。

看一个例子:

1
2
3
4
5
setTimeout(() => {
console.log('1秒后出现')
}, 1000)

console.log('我来啦')

上面这段代码由上而下执行,当遇到 setTimeout 后,发现是个异步任务,会将其放进Event Table中,注册异步任务回调函数,放进Event Queue;然后继续执行,console.log()是同步任务,放入主线程,执行之后输出我来啦;当主线程的任务执行完毕后,会去Event Queue中把刚刚注册的 setTimeout 异步函数任务放进主线程,执行后输出1秒后出现

宏任务(MacroTask)与微任务(MicroTask)

JavaScript 中的代码分为同步任务与异步任务,而异步任务有可以细分为宏任务微任务

setTimeoutsetInterval等都为宏任务;Promise.then catch finally为微任务。

当 JavaScript 执行过程中遇到宏任务时,会将其放进宏任务队列;遇到微任务,则放进微任务队列。微任务队列中的任务会先于宏任务执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log('script start')

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

Promise.resolve()
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')

上面这段代码,可将执行过程拆解为以下步骤:

  1. console.log('script start')为同步任务,放入主线程中
  2. setTimeout为异步宏任务,放进宏任务队列中
  3. Promise.resolve为异步微任务,放进微任务队列中
  4. console.log('script end')为同步任务,放进主线程中

将各自任务分配完毕,开始执行:

  1. 执行主线程中的代码,输出script startscript end
  2. 主线程中代码执行完毕,进入异步任务队列,优先寻找有无微任务
  3. 发现Promise.resolve,放进主线程中,执行.then()中的代码,输出promise1promise1
  4. 微任务中代码执行完毕,寻找宏任务中的代码
  5. 发现setTimeout,放进主线程中,输出setTimeout

所以最终结果为:

1
2
3
4
5
script start
script end
promise1
promise2
setTimeout

总结

JavaScript 代码可分为同步代码异步代码,异步代码又分为宏任务微任务

宏任务主要有setTimeoutsetInterval等。
微任务主要有Promise.then catch finally等。
微任务先于宏任务执行。

执行过程大致如下:

  1. 当遇到同步任务,会放进主线程
  2. 遇到异步任务,放进异步任务队列
  3. 主线程中的代码执行完毕后,去异步任务中读取代码
  4. 优先读取微任务,将其放进主线程中执行
  5. 主线程中的微任务代码执行完毕后,会去异步任务中读取宏任务,然后放进主线程中执行
  6. 主线程在执行微任务或宏任务的过程中,不断重复上述步骤

tip 参考文章
一次弄懂Event Loop(彻底解决此类面试问题)
这一次,彻底弄懂 JavaScript 执行机制
【THE LAST TIME】彻底吃透 JavaScript 执行机制