本文受:
(上)https://zhuanlan.zhihu.com/p/26229293
(下)https://zhuanlan.zhihu.com/p/26238030
这两篇知乎博文的启发,对事件循环机制进行分析与总结。
对于JavaScript中的单线程,拥有唯一的一个事件循环,事件循环就像是一个
while (true) {
// 执行一些代码...
}
一样,不断地去执行函数调用栈中的代码。在JavaScript代码执行时,除了依靠函数调用栈来搞定函数的执行顺序外,还要依靠任务队列来搞定另外一些代码的执行,但最终,任务队列中的代码总是会放到调用栈中去执行。
下面说一下事件循环机制中的几个重要的内容:
- 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
- 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
- macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
- micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)。
- setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
- 来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。
- 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
- 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
首先,我们通过一个稍微简单一点的例子去分析,后面熟悉了之后再上一个稍微复杂一点的例子。
// 为了方便理解,我以打印出来的字符作为当前的任务名称
setTimeout(function() {
console.log('timeout1');
})
new Promise(function(resolve) {
console.log('promise1');
for(var i = 0; i < 1000; i++) {
i == 99 && resolve();