Event-Loop 事件循环机制

309 阅读3分钟

说在前面

  1. JavaScript是单线程的语言,是从上往下依次执行,不存在代码同时执行。
  2. 在js引擎当中,所有的 JavaScript 代码被分为两类——同步代码、异步代码。

Event-Loop

究竟什么是事件循环呢?不急,我们先来看一段代码,并思考一下代码的打印顺序。

console.log('start');                       // 第 1 个打印

function foo() {
  console.log('foo');                       // 第 2 个打印
}
foo()

setTimeout(function() {
    console.log('setTimeout');              // 第 7 个打印
 }, 0)
  
new Promise(resolve => {
  console.log('Promise');                   // 第 3 个打印
  resolve()
})
  .then(function() {
    console.log('promise1');                // 第 5 个打印
  })
  .then(function() {
    console.log('promise2');                // 第 6 个打印
  })

console.log('end');                         // 第 4 个打印

以上的代码的打印顺序为:start、foo、Promise、end、promise1、promise2、setTimeout。或许此时你已经懵圈了,为什么会是这个结果呢?接下来我们来分析分析为何会这样。

代码在执行的过程中,遇到异步代码,会将异步代码用队列装起来(挂起)。而异步代码也分为两种:微任务宏任务

微任务队列:process.nextTick, promise.then, MutationObserver 等

宏任务队列:script, setTimeout, setInterval, setImmediate, I/O, UI-rendering 等

代码执行顺序:

  1. 首先执行同步代码,同步代码也属于宏任务
  2. 当执行完所有的同步代码后,执行栈为空,检查是否有异步代码要执行
  3. 执行异步代码中的微任务
  4. 执行完微任务后,在有必要的情况下会渲染页面
  5. 开启下一轮 Event-Loop,执行宏任务中的代码,每一个宏任务都会定义一个新的循环,我们可以将宏任务看作是上一轮循环的结束、新一轮循环的开始。

我们用一张流程图来具体展示一下代码执行顺序:

image.png

而在以上代码中,console.log('start') 属于同步代码,故直接执行,所以 start 第一个被打印;由于 foo 函数也为同步代码,故被调用时也可直接执行,所以 foo 第二个被打印;setTimeout 函数是异步代码,并且为宏任务,所以被挂起,不执行,setTimeout 函数去到宏任务队列;Promise 函数也属于同步函数,所以被执行,Promise 第三个被打印;Promise.then 为异步代码中的微任务,故也不执行,两个 Promise.then 依次去到微任务队列;console.log('end') 属于同步代码,被直接执行,end 第四个被打印。所有的同步代码执行完之后,检查到还有异步代码要执行。故我们先去微任务队列,依次执行两个 Promise.then ,故 promise1 第五个被打印promise2 第六个被打印,所有微任务执行完毕。接下来便去到宏任务队列,并开启了第二轮的事件循环,setTimeout 函数执行,setTimeout 第七个被打印

宏任务一定在微任务之后执行?

这是不对的,宏任务包括了同步代码,而同步代码在最先执行。若没有同步代码,且在同一轮循环中,宏任务才一定在微任务之后执行。