# 13. js的事件循环是什么?

参考答案:

事件循环是指: 指在单线程中处理任务时,通过不断地从任务队列中取出任务并执行,来保证任务能按正确的顺序被执行。

微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃) 宏任务 macrotask(task): setTimout / script / IO / UI Rendering

事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。

回答:

JavaScript 中的事件循环(Event Loop)是一种用于处理异步操作和事件的机制,它确保代码按照正确的顺序执行,并防止阻塞和死锁。

事件循环的工作原理如下:

  1. JavaScript 引擎会先执行同步代码,按照顺序执行函数调用和表达式求值,直到遇到异步操作或事件。

  2. 当遇到异步操作或事件时,它们将被放置在相应的任务队列(Task Queue)中,而不会立即执行。常见的任务队列包括: a. 宏任务队列(Macro Task Queue):包括整体的 script 代码、setTimeout、setInterval、I/O 操作等。 b. 微任务队列(Micro Task Queue):包括 Promise 回调、async/await、MutationObserver 等。

  3. 当同步代码执行完毕或遇到空闲时间时,JavaScript 引擎会检查微任务队列,并按照顺序执行队列中的微任务。执行完所有微任务后,才会执行下一轮的宏任务。

  4. 当一个宏任务执行时,如果它产生了新的异步操作或事件,这些新的任务将被放置在相应的任务队列中。

  5. 这个过程会不断重复,形成一个事件循环,直到所有的任务队列都被清空。

微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。

宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。

简而言之,每一次执行栈清空先清空微任务,然后从event loop中取出宏任务推入栈中执行作为下一个循环,结束后又清空微任务,直到执行栈,宏任务、微任务全部清空。


node中也有,事件循环是 node 处理非阻塞 I/O 操作的机制,node中事件循环的实现是依靠的libuv引擎。

Node中的Event loop和浏览器中的不相同。 Node的Event loop分为6个阶段,它们会按照顺序反复运行。 阶段概述:

  1. 定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数。
  2. I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调。
  3. 闲置阶段(idle, prepare):仅系统内部使用。
  4. 轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
  5. 检查阶段(check):setImmediate() 回调函数在这里执行
  6. 关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', ...)。

面试题:

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

// 1). 执行当前代码,将 setTimeout 和 Promise 添加到宏任务和微任务队列中。
// 2). 当前任务执行完毕,JavaScript 引擎先执行微任务队列中的 Promise 回调函数。
// 3). 微任务队列为空后,再执行宏任务队列中的 setTimeout 回调函数。