一盏茶🍵的时间,聊聊 js 事件循环(EventLoop)

2,303 阅读4分钟

我正在参加「掘金·启航计划」

前言

此篇文章笔者将和大家一起聊一聊面试中的高频考点:js 事件循环,以及事件循环中的宏任务和微任务。

js 单线程

早期 js 作为浏览器的脚本语言,主要是实现用户与浏览器之间的交互,以及操作 dom,这决定了它只能是单线程,否则会带来很复杂的同步问题。为了避免复杂性,从一诞生,js 就是单线程语言。

虽然 HTML5 提出了 Web Worker(运行在后台的 js,独立于其他脚本,不会影响页面的性能),允许 js 脚本创建多个线程,但是 子线程完全受主线程控制,且不得操作DOM。所以这个并没有改变 js 单线程的本质。

PS:浏览器是多进程的,不同类型的标签页会开启一个新的进程,相同类型的标签页会合并到一个进程。

同步任务和异步任务

同步任务

同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。

你可以理解为同一个时间,你只能干一件事。今天下班早,你给女朋友打电话,女朋友跟其他小伙伴一起吃饭去了,由于手机静音,没有听到,你就一直打,一直打,啥也没干,把时间都浪费了,这就叫同步。

异步任务

异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。

电话没打通,你就给女朋友发了个短信,洗澡去了,你回家了告诉我,(等我洗完了)再打给你,这就是异步。

任务队列和事件循环

js 运行时,除了一个正在运行的主线程,js 引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

在这个过程中,js 引擎在不停地检查,一遍又一遍,只要同步任务执行完了,js 引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

宏任务和微任务

除了广义的同步任务和异步任务,js 单线程中的任务可以细分为宏任务和微任务。

宏任务

宏任务包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O

微任务

微任务包括:process.nextTick(node 独有), Promise.then, Object.observe, MutationObserver

事件循环由一个宏任务队列+多个微任务队列组成。

执行顺序:首先,执行第一个宏任务:全局 script 脚本。产生的的宏任务和微任务进入各自的队列中。执行完 script 后,把当前的微任务队列清空,完成一次事件循环。

接着再取出一个宏任务,同样把在此期间产生的回调入队,再把当前的微任务队列清空,以此往复。

宏任务队列只有一个,而每一个宏任务都有一个自己的微任务队列,每轮循环都是由一个宏任务+多个微任务组成。

宏任务和微任务的执行流程

示例

现在我们来看一个简单的例子,以加深我们对事件循环的理解:

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("promise1");
  })
  .then(() => {
    console.log("promise2");
  });

console.log("script end");
  1. 执行整体代码,输出 script start,遇到 setTimeout,加入宏任务队列,当前宏任务队列(setTimeout),遇到 Promise.resolve().then(),加入微任务队列,当前微任务队列(promise1),输出 script end
  2. 执行微任务队列,输出 promise1.then 之后产生一个微任务,加入微任务队列,当前微任务队列(promise2),执行 .then,输出 promise2,执行宏任务,输出 setTimeout

参考:彻底理解js是单线程的

最后

以上就是笔者对 js 事件循环的理解,如有不足欢迎大家指出,如果大家觉得有点帮助的话,不要忘了点赞哟~