一篇文章让你理解透彻js事件循环(eventloop)

69 阅读4分钟

在观看该文章前,读者需要了解以下知识点(变量,函数,回调函数等),该文章有助于使你更好的理解js中的异步机制,详情可参考

js入门指南之Promise:从''承诺''到理解,告别回调地域

一、引用一道经典面试题

console.log(1);

new Promise((resolve) => {
  console.log(2);
  resolve()
})
  .then(() => {
    console.log(3);
    setTimeout(() => {
      console.log(4);
    }, 0)
  })

setTimeout(() => {
  console.log(5);
  setTimeout(() => {
    console.log(6);
  }, 0)
}, 0)

console.log(7);

请问该行代码的输出顺序是什么?为什么?

ps:输出顺序为1,2,7,3,5,4,6

你是否会产生好奇,明明我创建的这些定时器设定的时间都是0,以及.then()中的函数都没有设定时间延迟,为什么打印的顺序却要更晚呢,其实这些顺序差异的背后,都是js的核心机制----事件循环(event-loop)在发力

二、为什么js中会有事件循环(event-loop)?

此时你心中是否有一个问号,为什么js要有这个机制,起到了什么作用?

  1. 由于我们知道,js被设计成了单线程语言,所以同一时间内只能做一件事。这就是异步的原因。 参考:js入门指南之Promise:从''承诺''到理解,告别回调地域
  2. 设计成异步是为了避免所有任务(定时器,网络请求等)同步进行,避免遇到耗时操作时导致页面卡死,体验感差,
  3. 事件循环就是在异步的基础上,一种协调异步和异步任务执行的核心机制

三、事件循环这一机制是怎么运行的

  1. 调用栈开始执行(该执行任务本身为一个宏任务)
image.png 2. **遇到同步代码,开始执行同步代码**:v8引擎按照调用栈内的顺序,先进后出的顺序处理调用栈内的所有同步指令
  1. 当遇到异步代码时:(setTimeout,Promise,.then()等)此时v8引擎会将该代码挂起,使其在(web API)内处理,(可以理解为被丢在等待队列中),而不影响手头的工作,不会等待该耗时代码执行完毕(即使耗时代码被设置为耗时为0)
image.png
  1. v8引擎将调用栈内的同步任务全部执行完毕,调用栈为空

  2. 此时引擎检查微任务队列:(引擎优先查看微任务队列,不会立刻去看宏任务队列),此时引擎会连续不间断的将微任务队列内的任务处理完毕,直到微任务队列清空(ps:若又遇到耗时任务则依然会挂起)

image.png
  1. 可能进行UI渲染:游览器此时可能会趁机更新页面视图

  2. 检查宏任务队列,当微任务队列内的任务执行完毕后,引擎开始执行宏任务,优先执行宏任务内耗时短,先完成的任务,剩余耗时相同的任务,则遵循先进来先执行的规则

image.png

  1. 循环重复,又回到第二个步骤开始往下执行,实现无限循环,这就是事件循环

四、解读(一、)内的代码

(1) 调用栈开始执行(该执行任务本身为一个宏任务),(2)遇到同步代码,开始执行同步代码:v8引擎按照调用栈内的顺序,先进后出的顺序处理调用栈内的所有同步指令, (3)当遇到异步代码时:(setTimeout,Promise,.then()等)此时v8引擎会将该代码挂起,使其在(web API)内处理,(可以理解为被丢在等待队列中),而不影响手头的工作,不会等待该耗时代码执行完毕(即使耗时代码被设置为耗时为0)

简略表示图

image.png

(2) 此时引擎检查微任务队列:(引擎优先查看微任务队列,不会立刻去看宏任务队列),此时引擎会连续不间断的将微任务队列内的任务处理完毕,直到微任务队列清空(ps:若又遇到耗时任务则依然会挂起)

image.png

(3) 检查宏任务队列,当微任务队列内的任务执行完毕后,引擎开始执行宏任务,优先执行宏任务内耗时短,先完成的任务,剩余耗时相同的任务,则遵循先进来先执行的规则

image.png image.png

最后可知,打印结果为1,2,7,3,5,4,6

五、事件循环常见事件

  1. 如果一个微任务或一个宏任务中执行计算量极大的同步代码(如循环),则会阻塞后续的所有任务,即阻塞循环,导致页面无法响应

  2. 如果向微任务中不停添加新的微任务,则会导致宏任务永远无法执行

下篇文章将介绍js中async与await函数在异步事件中的作用,以及在事件循环中的本质,关注我,让你了解更多js底层原理