vlambda博客
学习文章列表

换个角度看 JS 的 Event Loop

Event Loop 直译就是“事件循环”,以往你看到这方面的文章,配图常常会有一个圆环。
诚然,圆环很形象化的体现了 Loop 的意思,但今天我想带你从另一个角度看看 JS 的事件机制。
在这个视角下,圆环会被我剪开改造成一条平直的传送带。每次触发的事件可以看成一个任务包,这个任务包从前到后分成 ABC 三个区域。
任务包里包含了两类操作:同步操作和会生成异步任务的操作。我们将这些操作按先来先执行的策略排好,放入任务包中的 A 区域。
现在我们将任务包放到传送带上,并启动传送带,将任务包送入引擎中。引擎会依次执行任务包 A 区域的操作。如果是同步操作,直接执行完事;如果是会生成异步任务的操作,那就要多费点心了。
根据产生异步 务的方法不同,异步任务 分为 宏任务 (Macro Task) 微任务 (Micro Task)
具体来说,通过 setTimeout、setInterval、setImmediate 这些 set* 方法生成的是宏任务。而 Promise.then、process.nextTick 生成的就是微任务了。
你问如何区分宏任务和微任务?对不起, 记忆尔~ 😂
宏任务和微任务除了名字的不同,在引擎中受到的待遇也是不同的:微任务会被放到 B 区域,宏任务会被放到 C 区域。
当 A 区域的操作执行完的时候,引擎会接着执行 B 区域的任务,最后是 C 区域的任务。所以微任务的优先级是高于宏任务的。
那么当 B 区域的任务里面又产生了新的微任务呢?这些微任务会被继续添加到微任务队列的队尾,也就是 B 区域的尾部。
看到这里,你能解释下 为什么 setTimeout 有计时不准的风险?
Talk is cheap, show me the code,下面就来看一段代码
你能说出答案吗?我们结合上面的内容来分析下。
首先 A 区域会依次执行 console.log(1) -> setTimeout -> new Promise(fn) -> console.log(7)。这里要注意: new Promise(fn) 里面的 fn 是同步 执行的。此时控制台会首先输出:1、3、7。
A 区域执行完之后,进入 B 区域执行任务,会依次执行 console.log(4) -> new Promise(fn),控制台输出:4、5。注意咯,B 区域中的 promise.then 又生成了一个微任务,这个微任务 console.log(6) 会被放入到 B 区域的队尾,被接着执行,控制台输出:6。
6 输出之后 B 区域已经没有任务了,引擎继续执行 C 区域的任务。
C 区域只有一个 setTimeout 生成的宏任务 console.log(2),所以引擎最后输出:2。
整合下,控制台输出的顺序是:1、3、7、4、5、6、2。你答对了吗?

今天就和你聊到这里,有疑惑的地方可以留言,我们一起探讨下 😊。你也可以再想想还有哪些方法会产生微任务和宏任务,欢迎留言交流。