什么是事件循环(Event Loop)?浏览器和 Node.js 中的事件循环有什么区别?

首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。

JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

什么是事件循环(Event Loop)?

事件循环是 JavaScript 实现异步编程的核心机制之一,用于协调同步代码、异步任务(宏任务、微任务)的执行顺序,避免代码阻塞。

同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环

详细说就是:先进行同步任务、执行过程中遇到宏任务或微任务放到各自队列。同步任务执行完了以后先查看微 **任务队列中有没有任务,执行队列所有任务以后再取一个宏任务进行执行,**这样一个循环往复的过程就是事件循环。管理异步API的回调函数什么时候回到主线程中执行。

事件循环的组成部分
  1. 调用栈(Call Stack):JavaScript引擎用来跟踪函数调用和返回值的栈结构。

  2. 任务队列(Task Queues):存储待执行的异步任务。不同的异步任务会被放入不同的队列中,如宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)。

  3. 事件循环(Event Loop):不断检查调用栈和任务队列,确保任务按顺序执行。

浏览器和 Node.js 中的事件循环有什么区别?

浏览器中的事件循环:

  1. 执行同步代码,遇到异步任务(宏任务、微任务)分别放入对应队列;
  2. 同步代码执行完毕后,清空所有微任务队列(按顺序执行);
  3. 微任务执行完毕后,执行一个宏任务,然后再次清空微任务队列,循环往复。
  • 宏任务:setTimeout、setInterval、DOM 事件、AJAX 请求;
  • 微任务:Promise.then/catch/finally、async/await、MutationObserver。

微任务通常比宏任务有更高的优先级,会在当前宏任务执行完毕后立即执行,但在下一个宏任务开始之前。

Node.js 中的事件循环:

在Node中的事件循环分为六个阶段:

  1. timer定时器阶段:执行如setTimeout和setInterval等的回调函数
  2. close callbacks关闭回调阶段:执行socket.close()事件回调
  3. check检查阶段:执行setImmediate的回调函数
  4. Poll轮询阶段:这是一个至关重要的阶段,系统主要做两件事,一是回到timer阶段执行回调,二是执行I/O回调。会主动检测是否有新的I/O事件,若存在新的I/O事件,则执行其回调函数,适当的条件下,node将阻塞在这里。
  5. Idle闲置阶段:仅供系统内部使用
  6. I/O回调 阶段:执行上一轮循环中未执行的I/O回调函数
  • 微任务:Promise.then/catch/finally、process.nextTick、queueMicrotask
  • 宏任务:setlnterval、setimeout、setlmmediate、I/O事件、close事件
注意:浏览器的事件循环和node事件循环有什么区别?

1、 微任务队列执行时机不同。在浏览器事件循环中, 每执行完一个宏任务,便要检查并执行微任务队列、而node事件循环中microtask 微任务会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 微任务队列的任务。

2、 特定API :Node.js有process.nextTicksetImmediate,而浏览器没有

浏览器事件循环流程: 执行一个宏任务 -> 清空所有微任务 -> 执行下一个宏任务 -> 清空所有微任务 -> ...

Node.js 事件循环流程: 执行完Timers阶段的所有宏任务 -> 清空所有微任务 -> 进入下一个阶段...

简单来说:

  1. 浏览器是一个宏任务+一个微任务队列
  2. node是一个宏任务队列+一个微任务队列

例子:

javascript 复制代码
setTimeout(()=>{
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(()=>{
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)

浏览器端运行结果:timer1=>promise1=>timer2=>promise2
Node 端运行结果:timer1=>timer2=>promise1=>promise2
javascript 复制代码
console.log('script开始');
setTimeout(() => {
    console.log('宏任务1');
    Promise.resolve().then(function () {
        console.log('微任务2')
    })
},0);

setTimeout(() => {
    console.log('宏任务2');
    Promise.resolve().then(function() {
        console.log('微任务3')
    })
})

Promise.resolve().then(function () {
    console.log('微任务1');
})

console.log('script结束');

//浏览器端运行结果
script开始
script结束
微任务1
宏任务1
微任务2
宏任务2
微任务3

//node端运行结果
script开始
script结束
微任务1
宏任务1
宏任务2
微任务2
微任务3