欢迎光临散文网 会员登陆 & 注册

事件循环

2023-08-08 02:57 作者:十三他很帅  | 我要投稿

事件循环及其相关概念:调用堆栈、任务队列与微任务队列

事件循环是 JavaScript 引擎中一个至关重要的部分,它使得 JavaScript 能够在单线程中以非阻塞的方式执行。要理解事件循环是如何工作的,我们需要首先了解一些关键概念,包括调用堆栈、任务、微任务及它们各自所在的队列。

调用堆栈(Call Stack)

调用堆栈是一种数据结构,用于跟踪正在执行的 JavaScript 代码中的函数调用。顾名思义,调用堆栈是一个堆栈,即内存中的后进先出(LIFO)数据结构。每个被执行的函数都表示为调用堆栈中的一个帧,会在前一个函数之上入栈。

让我们通过一个简单的例子来逐步了解这个过程:

  1. 调用堆栈最初为空。

  2. 函数 foo() 被压入调用堆栈。

  3. 函数 foo() 被执行并从调用堆栈中弹出。

  4. 函数 console.log('foo') 被推送到调用堆栈上。

  5. 函数 console.log('foo') 被执行并从调用堆栈中弹出。

  6. 函数 bar() 被压入调用堆栈。

  7. 函数 bar() 被执行并从调用堆栈中弹出。

  8. 函数 console.log('bar') 被推送到调用堆栈上。

  9. 函数 console.log('bar') 被执行并从调用堆栈中弹出。

  10. 调用堆栈现在为空。

任务(Task)与任务队列(Task Queue)

任务是预定的、同步执行的代码块。在执行过程中,它们对调用堆栈具有独占访问权限,并且可以将其他任务排入队列。任务之间可执行渲染更新。任务存储在任务队列中,并等待着相关函数执行。任务队列是一个先进先出(FIFO)的数据结构。典型的任务示例包括事件监听器回调函数以及 setTimeout() 的回调。

微任务(Microtask)与微任务队列(Microtask Queue)

微任务与任务类似,因为它们也是预定的同步执行的代码块,并在执行过程中对调用堆栈具有独占访问权限。此外,它们还存储在自己的先进先出(FIFO)数据结构中,即微任务队列。然而,微任务与任务的区别在于,它们会在当前任务执行完毕之后以及重新渲染之前立即执行。典型的微任务示例包括 Promise 回调和 MutationObserver 回调。

事件循环(Event Loop)

事件循环是一个持续运行并检查调用堆栈是否为空的过程。它通过将任务和微任务逐个放入调用堆栈中来处理它们,并对渲染过程进行控制。事件循环的核心包含四个关键步骤:

  1. 脚本评估:逐个执行脚本内的代码,直到调用堆栈为空。

  2. 任务处理:选择任务队列中的第一个任务,然后执行它,一直执行到调用堆栈为空。

  3. 微任务处理:从微任务队列中取出第一个微任务执行,直至调用堆栈为空,重复此过程直至微任务队列为空。

  4. 渲染:重新渲染 UI 界面,并循环回到步骤 2。

实际示例

为了更好地理解事件循环机制,我们可以看一个涵盖了上述所有概念的实际示例:

让我们逐步分析这个过程:

  1. 调用堆栈最初为空。事件循环开始评估脚本。

  2. console.log() 入栈并执行,输出 "Script start"。

  3. setTimeout() 入栈并执行。该操作会在任务队列中为其回调函数创建一个新任务。

  4. Promise.prototype.resolve() 入栈并执行,然后依次调用 Promise.prototype.then()

  5. Promise.prototype.then() 入栈并执行。这会在微任务队列中为其回调函数创建一个新的微任务。

  6. console.log() 入栈并执行,输出 "Script end"。

  7. 事件循环已完成其当前任务(评估脚本),接着开始处理微任务队列中的第一个微任务,即在步骤 5 中排入队列的 Promise.prototype.then() 的回调。

  8. console.log() 入栈并执行,输出 "Promise.then() #1"。

  9. Promise.prototype.then() 入栈并执行。这会在微任务队列中为其回调函数创建一个新条目。

  10. 事件循环检查微任务队列。因为它还不为空,所以会继续执行队列中的第一个微任务——在步骤 10 中排入队列的 Promise.prototype.then() 的回调。

  11. console.log() 入栈并执行,输出 "Promise.then() #2"。

  12. 如果有的话,在此处进行重新渲染。

  13. 由于微任务队列已经为空,事件循环将转到任务队列,并执行第一个任务。这是在步骤 3 中排入队列的 setTimeout() 的回调。

  14. console.log() 入栈并执行,输出 "setTimeout()"。

  15. 如果有的话,在此处进行重新渲染。

  16. 调用堆栈现在为空。

总结

  • 事件循环负责执行 JavaScript 代码。首先处理脚本评估和执行,然后处理任务和微任务。

  • 任务和微任务都是预定的同步代码块。它们逐个执行,并分别放置在任务队列和微任务队列中。

  • 调用堆栈跟踪 JavaScript 中的函数调用。

  • 每次执行微任务时,都需要清空微任务队列,然后才能进行下一个任务的处理。

  • 渲染工作发生在任务之间,但不会发生在微任务之间。

附加说明

  • 事件循环的脚本评估步骤本身与任务类似,即按顺序执行直至结束。

  • setTimeout() 的第二个参数表示执行前的最短延迟时间,而非保证时间。这是因为任务会按顺序执行,而微任务可能在此期间插入执行。

  • 在 Node.js 中,事件循环的行为类似于在浏览器中的行为。但它们之间也存在一些差异,最明显的区别是不存在渲染步骤。

  • 较旧版本的浏览器可能不会完全遵循这种操作顺序,因此任务和微任务可能会以不同的顺序执行。



事件循环的评论 (共 条)

分享到微博请遵守国家法律