事件循环
事件循环
前言
首先,得明确一点,JavaScript 是一门单线程语言。也就是说,JavaScript 代码是同步执行的,所有的代码都是由上而下执行的:
1 | var name = 'nick' |
但是如果代码量过大,有些代码的逻辑又复杂,一直不能运行完毕,后面的代码就无法运行,这就会造成一些堵塞的问题。就好比你去公交车站排队,等的人多了,你就必须等前面的人一个一个进入车里面,你才能再进去。
同步任务和异步任务
JavaScript 中,整体代码的运行我们可以称之为同步任务,也就是一行一行的执行;而另一些代码,可以不必等待前面的代码执行完毕之后再去执行,我们称之为异步任务。比方说,我们打开一个网页,页面骨架元素的加载就等同于同步任务,而网页上图片这种耗时的加载就等同于异步任务。
这里借用社区的一张图片:
解释一下图片上的内容:
- 当一段代码开始运行时,会检查里面的同步任务及异步任务;同步任务进入主线程,异步任务进入
Event Table
; Event Table
会注册异步任务回调函数,然后放进Event Queue
;- 当主线程中的任务全部执行完毕后,回去
Event Queue
中读取对应的异步任务,放进主线程中执行。 - 不断重复上面的步骤。
上述内容就是我们常说的事件循环(Event Loop
)。
看一个例子:
1 | setTimeout(() => { |
上面这段代码由上而下执行,当遇到 setTimeout 后,发现是个异步任务,会将其放进Event Table
中,注册异步任务回调函数,放进Event Queue
;然后继续执行,console.log()
是同步任务,放入主线程,执行之后输出我来啦
;当主线程的任务执行完毕后,会去Event Queue
中把刚刚注册的 setTimeout 异步函数任务放进主线程,执行后输出1秒后出现
。
宏任务(MacroTask)与微任务(MicroTask)
JavaScript 中的代码分为同步任务与异步任务,而异步任务有可以细分为宏任务与微任务。
setTimeout
、setInterval
等都为宏任务;Promise.then catch finally
为微任务。
当 JavaScript 执行过程中遇到宏任务时,会将其放进宏任务队列;遇到微任务,则放进微任务队列。微任务队列中的任务会先于宏任务执行。
1 | console.log('script start') |
上面这段代码,可将执行过程拆解为以下步骤:
console.log('script start')
为同步任务,放入主线程中setTimeout
为异步宏任务,放进宏任务队列中Promise.resolve
为异步微任务,放进微任务队列中console.log('script end')
为同步任务,放进主线程中
将各自任务分配完毕,开始执行:
- 执行主线程中的代码,输出
script start
,script end
- 主线程中代码执行完毕,进入异步任务队列,优先寻找有无微任务
- 发现
Promise.resolve
,放进主线程中,执行.then()
中的代码,输出promise1
,promise1
- 微任务中代码执行完毕,寻找宏任务中的代码
- 发现
setTimeout
,放进主线程中,输出setTimeout
所以最终结果为:
1 | script start |
总结
JavaScript 代码可分为同步代码和异步代码,异步代码又分为宏任务与微任务。
宏任务主要有setTimeout
、setInterval
等。
微任务主要有Promise.then catch finally
等。
微任务先于宏任务执行。
执行过程大致如下:
- 当遇到同步任务,会放进主线程
- 遇到异步任务,放进异步任务队列
- 主线程中的代码执行完毕后,去异步任务中读取代码
- 优先读取微任务,将其放进主线程中执行
- 主线程中的微任务代码执行完毕后,会去异步任务中读取宏任务,然后放进主线程中执行
- 主线程在执行微任务或宏任务的过程中,不断重复上述步骤
tip 参考文章