关于js的事件循环机制


JavaScript 的事件循环机制(Event Loop)是其运行时环境中的核心概念之一,用于处理异步操作和协调代码执行顺序。掌握事件循环对于理解 JavaScript 的异步编程至关重要。

JavaScript 的运行时环境

JavaScript 是一种单线程语言,意味着它在同一时刻只能执行一个任务。然而,JavaScript 的运行时环境(如浏览器或 Node.js)提供了一些机制来支持异步操作,而事件循环正是关键机制之一。

事件循环的基本概念

事件循环的核心目标是确保 JavaScript 程序能够高效地处理异步任务,同时避免阻塞主线程。它通过协调以下三个主要组件来实现这一目标:

  • 调用栈(Call Stack):用于存储当前正在执行的函数,采用 LIFO(后进先出)原则。

  • 任务队列(Task Queue):存储待执行的异步任务。

  • 事件循环(Event Loop):监控调用栈和任务队列,确保任务按顺序执行。

事件循环的工作原理

事件循环的工作原理可以概括为以下步骤:

  • 执行同步代码(主线程代码)。

  • 检查调用栈是否为空:如果调用栈为空,事件循环会从任务队列中取出任务执行。

  • 执行微任务队列中的任务(如 Promise 回调)。

  • 执行宏任务队列中的任务(如 setTimeout 回调)。

  • 重复上述过程,直到所有任务执行完毕。

任务的分类

在 JavaScript 中,任务主要分为两类:

  • 宏任务(Macrotask):包括 setTimeoutsetIntervalsetImmediate(Node.js)、I/O 任务、UI 渲染等。

  • 微任务(Microtask):包括 Promise.thenMutationObserverprocess.nextTick(Node.js)。

    • 注意:Promise 本身是一个同步函数,但它的回调(then、catch、finally)是异步执行的

      console.log("Start")
      
      const promise = new Promise((resolve, reject) => {
        console.log("Inside Promise")
        resolve("Resolved")
      })
      
      promise.then((res) => {
        console.log(res)
      })
      
      console.log("End")
      
      // 输出:
      Start
      Inside Promise
      End
      Resolved

宏任务与微任务的执行顺序

事件循环在执行任务时,会优先处理微任务队列中的任务。具体执行顺序如下:

  • 执行当前宏任务(如 setTimeout 的回调)。

  • 执行微任务队列中的所有任务(如 Promise.then)。

  • 处理下一个宏任务,并重复以上过程。

示例代码

以下是一个示例,展示事件循环如何处理宏任务和微任务:

console.log("1")

setTimeout(() => {
  console.log("2")
}, 0)

Promise.resolve().then(() => {
  console.log("3")
})

console.log("4")

执行过程分析

  • console.log(“1”) 是同步代码,立即执行,输出 1。

  • setTimeout 是宏任务,回调函数被放入宏任务队列。

  • Promise.then 是微任务,回调函数被放入微任务队列。

  • console.log(“4”) 是同步代码,立即执行,输出 4。

  • 执行微任务 console.log(“3”),输出 3。

  • 执行宏任务 console.log(“2”),输出 2。

最终输出顺序

1
4
3
2

Node.js 事件循环的不同之处

在 Node.js 中,事件循环与浏览器环境略有不同,特别是 process.nextTick 和 setImmediate 的处理方式:

console.log("A")

setTimeout(() => console.log("B"), 0)

process.nextTick(() => console.log("C"))

Promise.resolve().then(() => console.log("D"))

setImmediate(() => console.log("E"))

console.log("F")

执行顺序

  • 同步代码:AF

  • process.nextTick(优先级最高):C

  • Promise.then:D

  • 宏任务队列(setTimeout、setImmediate):

    • setTimeout("B")

    • setImmediate("E")

最终输出顺序

A
F
C
D
B
E

事件循环的意义

事件循环机制使得 JavaScript 能够在单线程环境下高效地处理异步任务,避免了线程阻塞问题,同时保证了代码的执行顺序和逻辑的正确性。

总结

  • 事件循环:协调调用栈和任务队列,确保任务按顺序执行。

  • 宏任务和微任务:宏任务包括 setTimeout 等,微任务包括 Promise.then 等。

  • 执行顺序:优先执行同步代码 -> 执行微任务 -> 执行宏任务。


  目录