JS事件循环

JS事件循环

众所周知,JS是一门单线程语言,但是它却又很多异步特性。这是因为它有自己的并发模型,基于任务队列和事件循环。

任务队列

当用户触发一个事件,如果该事件有处理器handler,那么该事件就被当成一个任务推入任务队列里。
为什么不是直接执行该事件处理器呢?

因为JS单线程的原因,同一个时间点只能执行一段代码。一个frame中的UIDOM用户输入都是由一个线程完成。那么一旦某个事件处理器的代码逻辑执行时间过长,那么体现出来的就是页面卡顿无响应。

使用任务队列就可以缓解这个问题,同一时间可以有多个事件被触发,多个用户输入,这些都被作为任务记录下来,推入到任务队列中,并且暂时并不去立即执行。

这些任务如何进行处理,处理逻辑是什么?就牵扯到了事件循环。

事件循环

JS引擎中有栈,堆,和任务队列。其中栈负责函数的调用,堆存储对象,而任务队列则负责异步事件的处理。

通常一个函数A被调用,就会被push到栈中,如果这个函数还调用了B,那么B也会被push到栈中。直到函数执行完毕,才会被弹出栈。

当栈为空时,表示前一个函数逻辑已经执行完毕,等待下一步操作。这个时候,任务队列就会将队列中的任务(事件处理器)推入栈中执行。然后再等待栈空,继续推入下一个任务(循环)。直到任务队列也为空。

每一个任务完整地执行后,其它任务才会被执行。

JS中的所有异步操作/事件处理器都是将回调函数推入任务队列中,其具体执行时间得看它前面的任务执行时长。

setTimeout

说起任务队列就得不得不提的setTimeout(fn, timeout),其中的timeout是指至少在多少毫秒将回调函数fn推入任务队列。
也就是说fn进入任务队列的时长是大于等于timeout的,其真正的执行时间间隔,也是大于timeout的。

永不阻塞

由于任务队列的性质,JS将每个操作都存储了下来,所以它不会阻塞(除非你的代码)。

宏任务和微任务

一个事件循环中,会有一个或多个任务队列和一个微任务队列。有了一个任务队列,为什么还要一个微任务队列呢?

首先,在宏任务之间,是会存在渲染的。但是微任务不会,微任务属于一个宏任务。
那么哪些操作会发起一个微任务呢?promisemutation observer的回调都会添加一个微任务。当当前调用栈为空时,微任务队列就会出列并且执行,直到微任务队列为空并且调用栈为空,才会开始下一个宏任务。

参考

上一篇 重学SVG
下一篇 单链表算法的基本思路