前端面试之JavaScript基础(七)—— Event Loop

770 阅读4分钟

事件循环(Event Loop)机制是异步编程的关键知识点,理解该机制有利于我们更好的理解和书写异步代码。我们都知道 JavaScript 是基于 单线程 设计的,当我们的代码都是同步执行时就会很容易产生 阻塞。此时,为了避免阻塞的发生就诞生了 异步编程 ,为了支撑异步编程代码运作就产生了 事件循环 机制。接下来,我们通过一张图来了解事件循环究竟是什么: 上图展示的是 JavaScript 的基础线程架构,运行时 主线程 会从维护异步事件的 消息队列 中取出任务并执行。执行时有的任务是从网络上下载资源,有的任务是文件读取,当这些任务又需要异步处理时就会被包装成新的任务再次加入消息队列,如此循环往复的取出并执行的工作机制就被称为 事件循环机制

宏任务与微任务

当我们了解了事件循环机制后,我们就来看看与其有紧密联系的概念 宏任务微任务

  • 宏任务:在消息队列中等待被主线程执行的事件
  • 微任务:一个需要异步执行的事件,执行时机是在主任务执行结束之后、当前宏任务结束之前。

宏任务

function foo() {
  console.log('success')
}

setTimeout(foo, 1000)

产生宏任务的典型代表就是 setTimeout ,我们通过分析上述这段代码来看看宏任务是什么及其执行时机。 从图中可以看出,每产生一个宏任务就会被加入消息队列的尾部,主线程会不断的从消息队列的头部位置取出宏任务执行。

微任务

主线程执行宏任务有一个比较明显的缺点,那就是时间颗粒度的把控太粗糙,无法胜任对精度和实时性较高的场景。当消息队列当中有一个任务执行时间过长时,就会影响到排在其后面的事件,为了解决这个不可控的情况就引入了微任务的概念。

Promise.resolve 可以生成一个微任务,接下来我们就用这个方法结合 主线程消息队列调用栈 来了解微任务。

function foo() {
  console.log('foo')
  Promise.resolve().then(() => console.log('微任务 - foo'))
  setTimeout(() => console.log('宏任务 - foo'), 0)
}

foo()

console.log('global')
Promise.resolve().then(() => console.log('微任务 - global'))
setTimeout(() => console.log('宏任务 - global'), 0)

在这段代码中,包含了通过 setTimeout 产生的宏任务和 Promise.resolve 创建的微任务,它的最终打印结果是:

foo
global
微任务 - foo
微任务 - global
宏任务 - foo
宏任务 - global

接下来我们通过执行过程来分析微任务的执行,请看下图: 上图展示了一部分执行过程,从图中可以看到除了存放宏任务的消息队列外还多了一个 微任务队列。JavaScript 引擎在工作时会在当前执行的主任务当中维护一个微任务队列,当该任务 快要执行结束时 就会将微任务队列当中的任务依次取出并 全部执行完毕 再执行下一个宏任务。

完整的执行过程如下:

  1. 主任务执行时,首先调用 foo 函数执行同步代码 console.log('foo')
  2. Promise.resolve 执行会向微任务队列中加入执行 console.log('微任务 - foo') 的事件;
  3. setTimeout 执行会向消息队列中加入执行 console.log('宏任务 - foo') 的事件;
  4. 主任务继续执行,执行同步代码 console.log('global')
  5. Promise.resolve 执行会向微任务队列中加入执行 console.log('微任务 - global') 的事件;
  6. setTimeout 执行会向消息队列中加入执行 console.log('宏任务 - global') 的事件;
  7. 此时主任务已经快执行结束,这时引擎会依次执行微任务队列当中的内容,直到清空该队列就结束当前任务;
  8. 主线程从消息队列当中继续取下一个任务执行。

小结

今天我们讲了事件循环机制和宏任务与微任务的概念,总结一下:

  • 事件循环机制是为主线程调度异步任务的一种机制,主线程会循环的从消息队列中取出任务并执行,直至消息队列清空便会挂起,消息队列当中出现新任务有回继续执行。
  • 宏任务是消息队列当中待执行的任务,宏任务主要有script、setImmediate、 setTimeout/setInterval、IO 事件。
  • 微任务是为了解决宏任务的时间颗粒度太粗糙而产生的,它会在当前主任务结束后,宏任务结束前全部执行,微任务主要有 Promise、MutationObserver、process.nextTick。